Skip to content

Commit 92afed0

Browse files
authored
Merge pull request #1079 from compas-dev/occ_rhino_brep
Occ rhino brep
2 parents 331c38b + 1949f78 commit 92afed0

File tree

6 files changed

+166
-26
lines changed

6 files changed

+166
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3333
* Fixed bug in `intersections.intersection_circle_circle_xy` where the Circle's Plane was accessed instead of the centre.
3434
* Fixed bug in `_core.tangent` where the Circle's Plane was accessed instead of the centre.
3535
* Fixed the `test_tangent` to work with a properly defined circle
36-
36+
* `RhinoBrep` serialization works now with surface types other than NURBS.
3737
### Removed
3838

3939

src/compas_rhino/geometry/brep/brep.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def points(self):
103103
@property
104104
def edges(self):
105105
if self._brep:
106-
return [RhinoBrepEdge(edge) for edge in self._brep.Edges]
106+
return [RhinoBrepEdge(trim) for trim in self._brep.Trims]
107107

108108
@property
109109
def loops(self):

src/compas_rhino/geometry/brep/edge.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,30 @@
55
from compas_rhino.geometry import RhinoNurbsCurve
66
from compas_rhino.conversions import curve_to_compas_line
77
from compas_rhino.conversions import curve_to_compas_circle
8+
from compas_rhino.conversions import curve_to_compas_ellipse
89
from compas_rhino.conversions import line_to_rhino_curve
910
from compas_rhino.conversions import circle_to_rhino_curve
11+
from compas_rhino.conversions import ellipse_to_rhino_curve
12+
1013

1114
from .vertex import RhinoBrepVertex
1215

1316

1417
class RhinoBrepEdge(BrepEdge):
15-
"""A wrapper for Rhino.Geometry.BrepEdge
18+
"""A wrapper for Rhino.Geometry.BrepEdge.
19+
20+
The expected native type here is a Rhino.Geometry.BrepTrim.
21+
a BrepTrim holds a reference to its associated BrepEdge as well as its start a end vertices
22+
in a correct topological order (!).
1623
1724
Attributes
1825
----------
1926
curve : :class:`Rhino.Geometry.Curve3D`
2027
The underlying geometry of this edge.
2128
start_vertex : :class:`~compas_rhino.geometry.RhinoBrepVertex`, read-only
22-
The start vertex of this edge.
29+
The start vertex of this edge (taken from BrepTrim).
2330
end_vertex : :class:`~compas_rhino.geometry.RhinoBrepVertex`, read-only
24-
The end vertex of this edge.
31+
The end vertex of this edge (taken from BrepTrim).
2532
vertices : list[:class:`~compas_rhino.geometry.RhinoBrepVertex`], read-only
2633
The list of vertices which comprise this edge (start and end)
2734
is_circle : bool, read-only
@@ -31,20 +38,20 @@ class RhinoBrepEdge(BrepEdge):
3138
3239
"""
3340

34-
def __init__(self, rhino_edge=None):
41+
def __init__(self, rhino_trim=None):
3542
super(RhinoBrepEdge, self).__init__()
3643
self._edge = None
3744
self._curve = None
3845
self._start_vertex = None
3946
self._end_vertex = None
40-
if rhino_edge:
41-
self._set_edge(rhino_edge)
47+
if rhino_trim:
48+
self._set_edge(rhino_trim)
4249

43-
def _set_edge(self, native_edge):
44-
self._edge = native_edge
50+
def _set_edge(self, rhino_trim):
51+
self._edge = rhino_trim.Edge
4552
self._curve = self._edge.EdgeCurve
46-
self._start_vertex = RhinoBrepVertex(self._edge.StartVertex)
47-
self._end_vertex = RhinoBrepVertex(self._edge.EndVertex)
53+
self._start_vertex = RhinoBrepVertex(rhino_trim.StartVertex)
54+
self._end_vertex = RhinoBrepVertex(rhino_trim.EndVertex)
4855

4956
# ==============================================================================
5057
# Data
@@ -58,13 +65,16 @@ def data(self):
5865
elif self.is_circle:
5966
type_ = "circle"
6067
curve = curve_to_compas_circle(self._curve)
68+
elif self.is_ellipse:
69+
type_ = "ellipse"
70+
curve = curve_to_compas_ellipse(self._curve)
6171
else:
6272
type_ = "nurbs"
6373
curve = RhinoNurbsCurve.from_rhino(self._curve)
6474
return {
6575
"type": type_,
6676
"value": curve.data,
67-
"points": [self._start_vertex.point.data, self._end_vertex.point.data],
77+
"points": [self.start_vertex.point.data, self.end_vertex.point.data],
6878
}
6979

7080
@data.setter
@@ -78,9 +88,11 @@ def data(self, value):
7888
self._curve = circle_to_rhino_curve(
7989
Circle.from_data(value["value"])
8090
) # this returns a Nurbs Curve, why?
91+
elif curve_type == "ellipse":
92+
self._curve = ellipse_to_rhino_curve(value["value"])
8193
else:
8294
self._curve = RhinoNurbsCurve.from_data(value["value"]).rhino_curve
83-
# TODO: can a single edge be defined with more than start and end vertices?
95+
8496
self._start_vertex, self._end_vertex = RhinoBrepVertex(), RhinoBrepVertex()
8597
self._start_vertex._point = Point.from_data(value["points"][0])
8698
self._end_vertex._point = Point.from_data(value["points"][1])
@@ -103,7 +115,7 @@ def end_vertex(self):
103115

104116
@property
105117
def vertices(self):
106-
return [self._start_vertex, self._end_vertex]
118+
return [self.start_vertex, self.end_vertex]
107119

108120
@property
109121
def is_circle(self):
@@ -112,3 +124,7 @@ def is_circle(self):
112124
@property
113125
def is_line(self):
114126
return self._curve.IsLinear()
127+
128+
@property
129+
def is_ellipse(self):
130+
return self._curve.IsEllipse()

src/compas_rhino/geometry/brep/face.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
from compas.geometry import BrepFace
22
from compas_rhino.geometry import RhinoNurbsSurface
3+
from compas_rhino.conversions import plane_to_compas
4+
from compas_rhino.conversions import sphere_to_compas
5+
from compas_rhino.conversions import cylinder_to_compas
6+
from compas_rhino.conversions import plane_to_rhino
7+
from compas_rhino.conversions import sphere_to_rhino
8+
from compas_rhino.conversions import cylinder_to_rhino
39

410
from .loop import RhinoBrepLoop
511

@@ -35,9 +41,7 @@ def __init__(self, rhino_face=None):
3541
def _set_face(self, native_face):
3642
self._face = native_face
3743
self._loops = [RhinoBrepLoop(loop) for loop in self._face.Loops]
38-
self._surface = RhinoNurbsSurface.from_rhino(
39-
self._face.ToNurbsSurface()
40-
) # surface in Rhino will always be NURBS
44+
self._surface = self._face.UnderlyingSurface()
4145

4246
# ==============================================================================
4347
# Data
@@ -47,26 +51,42 @@ def _set_face(self, native_face):
4751
def data(self):
4852
boundary = self._loops[0].data
4953
holes = [loop.data for loop in self._loops[1:]]
50-
surface = {"type": "nurbs"}
51-
surface.update(self.surface.data)
52-
return {"boundary": boundary, "surface": surface, "holes": holes}
54+
surface_type, surface = self._get_surface_geometry(self._surface)
55+
surface_data = {"value": surface.data, "type": surface_type}
56+
return {"boundary": boundary, "surface": surface_data, "holes": holes}
5357

5458
@data.setter
5559
def data(self, value):
5660
boundary = RhinoBrepLoop.from_data(value["boundary"])
5761
holes = [RhinoBrepLoop.from_data(loop) for loop in value["holes"]]
5862
self._loops = [boundary] + holes
59-
# TODO: should we check surface type here? should we support surfaces other than NURBS?
60-
self._surface = RhinoNurbsSurface.from_data(value["surface"])
63+
64+
# TODO: using the new serialization mechanism, surface.to_nurbs() should replace all this branching..
65+
# TODO: given that Plane, Sphere, Cylinder etc. all implement to_nurbs()
66+
surface_data = value["surface"]
67+
type_ = surface_data["type"]
68+
surface = surface_data["value"]
69+
if type_ == "plane":
70+
surface = self._make_surface_from_loop(boundary)
71+
elif type_ == "sphere":
72+
surface = RhinoNurbsSurface.from_sphere(Sphere.from_data(surface))
73+
elif type_ == "cylinder":
74+
surface = RhinoNurbsSurface.from_cylinder(Cylinder.from_data(surface))
75+
elif type_ == "nurbs":
76+
surface = RhinoNurbsSurface.from_data(surface)
77+
elif type_ == "torus":
78+
raise NotImplementedError(
79+
"Support for torus surface is not yet implemented!"
80+
)
81+
self._surface = surface.rhino_surface
6182

6283
# ==============================================================================
6384
# Properties
6485
# ==============================================================================
6586

6687
@property
6788
def native_surface(self):
68-
if self._surface:
69-
return self._surface.rhino_surface
89+
return self._surface
7090

7191
@property
7292
def loops(self):
@@ -87,3 +107,28 @@ def holes(self):
87107
@property
88108
def is_plane(self):
89109
return
110+
111+
@staticmethod
112+
def _get_surface_geometry(surface):
113+
success, cast_surface = surface.TryGetPlane()
114+
if success:
115+
return "plane", plane_to_compas(cast_surface)
116+
success, cast_surface = surface.TryGetSphere()
117+
if success:
118+
return "sphere", sphere_to_compas(cast_surface)
119+
success, cast_surface = surface.TryGetCylinder()
120+
if success:
121+
return "cylinder", cylinder_to_compas(cast_surface)
122+
success, cast_surface = surface.TryGetTorus()
123+
if success:
124+
raise NotImplementedError(
125+
"Support for torus surface is not yet implemented!"
126+
)
127+
return "nurbs", RhinoNurbsSurface.from_rhino(surface.ToNurbsSurface())
128+
129+
@staticmethod
130+
def _make_surface_from_loop(loop):
131+
# order of corners determines the normal of the resulting surface
132+
corners = [loop.edges[i].start_vertex.point for i in range(4)]
133+
surface = RhinoNurbsSurface.from_corners(corners)
134+
return surface

src/compas_rhino/geometry/brep/loop.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import Rhino
44

55
from .edge import RhinoBrepEdge
6+
from .vertex import RhinoBrepVertex
67

78

89
class LoopType(object):
@@ -54,7 +55,7 @@ def __init__(self, rhino_loop=None):
5455
def _set_loop(self, native_loop):
5556
self._loop = native_loop
5657
self._type = int(self._loop.LoopType)
57-
self._edges = [RhinoBrepEdge(t.Edge) for t in self._loop.Trims]
58+
self._edges = [RhinoBrepEdge(trim) for trim in self._loop.Trims]
5859

5960
# ==============================================================================
6061
# Data

src/compas_rhino/geometry/surfaces/surface.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
from compas_rhino.conversions import plane_to_compas_frame
1111
from compas_rhino.conversions import box_to_compas
1212
from compas_rhino.conversions import xform_to_rhino
13+
from compas_rhino.conversions import plane_to_rhino
14+
from compas_rhino.conversions import sphere_to_rhino
15+
from compas_rhino.conversions import cylinder_to_rhino
1316

1417
from compas_rhino.geometry.curves import RhinoCurve
1518

@@ -76,6 +79,81 @@ def is_v_periodic(self):
7679
# Constructors
7780
# ==============================================================================
7881

82+
@classmethod
83+
def from_corners(cls, corners):
84+
"""Creates a NURBS surface using the given 4 corners.
85+
86+
The order of the given points determins the normal direction of the generated surface.
87+
88+
Parameters
89+
----------
90+
corners : list(:class:`~compas.geometry.Point`)
91+
4 points in 3d space to represent the corners of the planar surface.
92+
93+
Returns
94+
-------
95+
:class:`~compas_rhino.geometry.RhinoNurbsSurface`
96+
97+
"""
98+
rhino_points = [
99+
Rhino.Geometry.Point3d(corner.x, corner.y, corner.z) for corner in corners
100+
]
101+
return cls.from_rhino(
102+
Rhino.Geometry.NurbsSurface.CreateFromCorners(*rhino_points)
103+
)
104+
105+
@classmethod
106+
def from_sphere(cls, sphere):
107+
"""Creates a NURBS surface from a sphere.
108+
109+
Parameters
110+
----------
111+
sphere : :class:`~compas.geometry.Sphere`
112+
The surface's geometry.
113+
114+
Returns
115+
-------
116+
:class:`~compas_rhino.geometry.RhinoNurbsSurface`
117+
118+
"""
119+
sphere = sphere_to_rhino(sphere)
120+
surface = Rhino.Geometry.NurbsSurface.CreateFromSphere(sphere)
121+
return cls.from_rhino(surface)
122+
123+
@classmethod
124+
def from_cylinder(cls, cylinder):
125+
"""Create a NURBS surface from a cylinder.
126+
127+
Parameters
128+
----------
129+
cylinder : :class:`~compas.geometry.Cylinder`
130+
The surface's geometry.
131+
132+
Returns
133+
-------
134+
:class:`~compas_rhino.geometry.RhinoNurbsSurface`
135+
136+
"""
137+
cylinder = cylinder_to_rhino(cylinder)
138+
surface = Rhino.Geometry.NurbsSurface.CreateFromCylinder(cylinder)
139+
return cls.from_rhino(surface)
140+
141+
@classmethod
142+
def from_torus(cls, torus):
143+
"""Create a NURBS surface from a torus.
144+
145+
Parameters
146+
----------
147+
torus : :class:`~compas.geometry.Torus`
148+
The surface's geometry.
149+
150+
Returns
151+
-------
152+
:class:`~compas_rhino.geometry.RhinoNurbsSurface`
153+
154+
"""
155+
raise NotImplementedError
156+
79157
@classmethod
80158
def from_rhino(cls, rhino_surface):
81159
"""Construct a NURBS surface from an existing Rhino surface.

0 commit comments

Comments
 (0)