Skip to content

Commit f3fafd1

Browse files
authored
Merge pull request #1064 from compas-dev/brep_boolean_ops
Brep boolean ops
2 parents b0e56b5 + 5264362 commit f3fafd1

File tree

6 files changed

+171
-2
lines changed

6 files changed

+171
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
* Added pluggable `Brep` support with `compas.geometry.brep`.
1717
* Added Rhino `Brep` plugin in `compas_rhino.geometry.brep`.
18+
* Added boolean operations to the `compas_rhino` `Brep` backend.
19+
* Added boolean operation operator overloads in `compas.geometry.Brep`
1820

1921
### Changed
2022
* Based all gltf data classes on `BaseGLTFDataClass`
2123

2224
* Fixed `Color.__get___` AttributeError.
25+
* Fixed `cylinder_to_rhino` conversion to match `compas.geometry.Cylinder` location.
2326

2427
### Removed
2528

src/compas/geometry/brep/brep.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,63 @@ def from_boolean_union(cls, brep_a, brep_b):
607607
"""
608608
raise NotImplementedError
609609

610+
def __sub__(self, other):
611+
"""Compute the boolean difference using the "-" operator of this shape and another.
612+
613+
Parameters
614+
----------
615+
other : :class:`~compas.geometry.Brep`
616+
The other Brep to create a union with.
617+
618+
Returns
619+
-------
620+
:class:`~compas.geometry.Brep`
621+
The Brep resulting from the difference operation.
622+
623+
"""
624+
results = type(self).from_boolean_difference(self, other)
625+
if isinstance(results, list):
626+
results = results[0]
627+
return results
628+
629+
def __and__(self, other):
630+
"""Compute the boolean intersection using the "&" operator of this shape and another.
631+
632+
Parameters
633+
----------
634+
other : :class:`~compas.geometry.Brep`
635+
The other Brep to create a union with.
636+
637+
Returns
638+
-------
639+
:class:`~compas.geometry.Brep`
640+
The Brep resulting from the intersection operation.
641+
642+
"""
643+
results = type(self).from_boolean_intersection(self, other)
644+
if isinstance(results, list):
645+
results = results[0]
646+
return results
647+
648+
def __add__(self, other):
649+
"""Compute the boolean union using the "+" operator of this Brep and another.
650+
651+
Parameters
652+
----------
653+
other : :class:`~compas.geometry.Brep`
654+
The other Brep to create a union with.
655+
656+
Returns
657+
-------
658+
:class:`~compas.geometry.Brep`
659+
The Brep resulting from the union operation.
660+
661+
"""
662+
results = type(self).from_boolean_union(self, other)
663+
if isinstance(results, list):
664+
results = results[0]
665+
return results
666+
610667
# ==============================================================================
611668
# Converters
612669
# ==============================================================================

src/compas_rhino/conversions/_shapes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,7 @@ def cylinder_to_rhino(cylinder):
158158
:rhino:`Rhino.Geometry.Cylinder`
159159
160160
"""
161-
return RhinoCylinder(circle_to_rhino(cylinder.circle), cylinder.height)
161+
circle = cylinder.circle.copy()
162+
height = cylinder.height
163+
circle.plane.point += circle.plane.normal * (-0.5 * height)
164+
return RhinoCylinder(circle_to_rhino(circle), cylinder.height)

src/compas_rhino/geometry/brep/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,8 @@ def from_brep(*args, **kwargs):
3636
@plugin(category="factories", requires=["Rhino"])
3737
def from_box(*args, **kwargs):
3838
return RhinoBrep.from_box(*args, **kwargs)
39+
40+
41+
@plugin(category="factories", requires=["Rhino"])
42+
def from_cylinder(*args, **kwargs):
43+
return RhinoBrep.from_cylinder(*args, **kwargs)

src/compas_rhino/geometry/brep/brep.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from compas_rhino.conversions import point_to_rhino
99
from compas_rhino.conversions import xform_to_rhino
1010
from compas_rhino.conversions import frame_to_rhino
11+
from compas_rhino.conversions import cylinder_to_rhino
1112

1213
import Rhino
1314

@@ -166,6 +167,23 @@ def from_box(cls, box):
166167
rhino_box = box_to_rhino(box)
167168
return cls.from_brep(rhino_box.ToBrep())
168169

170+
@classmethod
171+
def from_cylinder(cls, cylinder):
172+
"""Create a RhinoBrep from a box.
173+
174+
Parameters
175+
----------
176+
box : :class:`~compas.geometry.Box`
177+
The box geometry of the brep.
178+
179+
Returns
180+
-------
181+
:class:`~compas_rhino.geometry.RhinoBrep`
182+
183+
"""
184+
rhino_cylinder = cylinder_to_rhino(cylinder)
185+
return cls.from_brep(rhino_cylinder.ToBrep(True, True))
186+
169187
# ==============================================================================
170188
# Methods
171189
# ==============================================================================
@@ -211,6 +229,87 @@ def trim(self, trimming_plane, tolerance=TOLERANCE):
211229

212230
self._brep = results[0]
213231

232+
@classmethod
233+
def from_boolean_difference(cls, breps_a, breps_b):
234+
"""Construct a Brep from the boolean difference of two groups of Breps.
235+
236+
Parameters
237+
----------
238+
breps_a : :class:`~compas_rhino.geometry.RhinoBrep` or list(:class:`~compas_rhino.geometry.RhinoBrep`)
239+
One or more Breps from which to substract.
240+
breps_b : :class:`~compas_rhino.geometry.RhinoBrep` or list(:class:`~compas_rhino.geometry.RhinoBrep`)
241+
One or more Breps to substract.
242+
243+
Returns
244+
-------
245+
list(:class:`~compas_rhino.geometry.RhinoBrep`)
246+
list of one or more resulting Breps.
247+
248+
"""
249+
if not isinstance(breps_a, list):
250+
breps_a = [breps_a]
251+
if not isinstance(breps_b, list):
252+
breps_b = [breps_b]
253+
resulting_breps = Rhino.Geometry.Brep.CreateBooleanDifference(
254+
[b.native_brep for b in breps_a],
255+
[b.native_brep for b in breps_b],
256+
TOLERANCE
257+
)
258+
return [RhinoBrep.from_brep(brep) for brep in resulting_breps]
259+
260+
@classmethod
261+
def from_boolean_union(cls, breps_a, breps_b):
262+
"""Construct a Brep from the boolean union of two groups of Breps.
263+
264+
Parameters
265+
----------
266+
breps_a : :class:`~compas_rhino.geometry.RhinoBrep` or list(:class:`~compas_rhino.geometry.RhinoBrep`)
267+
One of more breps to join.
268+
breps_b : :class:`~compas_rhino.geometry.RhinoBrep` or list(:class:`~compas_rhino.geometry.RhinoBrep`)
269+
Another one of more breps to join.
270+
271+
Returns
272+
-------
273+
list(:class:`~compas_rhino.geometry.RhinoBrep`)
274+
list of one or more resulting Breps.
275+
276+
"""
277+
if not isinstance(breps_a, list):
278+
breps_a = [breps_a]
279+
if not isinstance(breps_b, list):
280+
breps_b = [breps_b]
281+
282+
resulting_breps = Rhino.Geometry.Brep.CreateBooleanUnion([b.native_brep for b in breps_a + breps_b], TOLERANCE)
283+
return [RhinoBrep.from_brep(brep) for brep in resulting_breps]
284+
285+
@classmethod
286+
def from_boolean_intersection(cls, breps_a, breps_b):
287+
"""Construct a Brep from the boolean intersection of two groups of Breps.
288+
289+
Parameters
290+
----------
291+
breps_a : :class:`~compas_rhino.geometry.RhinoBrep` or list(:class:`~compas_rhino.geometry.RhinoBrep`)
292+
One or more Breps to instrsect.
293+
breps_b : :class:`~compas_rhino.geometry.RhinoBrep` or list(:class:`~compas_rhino.geometry.RhinoBrep`)
294+
Another one or more Breps to intersect.
295+
296+
Returns
297+
-------
298+
list(:class:`~compas_rhino.geometry.RhinoBrep`)
299+
list of one or more resulting Breps.
300+
301+
"""
302+
if not isinstance(breps_a, list):
303+
breps_a = [breps_a]
304+
if not isinstance(breps_b, list):
305+
breps_b = [breps_b]
306+
resulting_breps = Rhino.Geometry.Brep.CreateBooleanIntersection(
307+
[b.native_brep for b in breps_a],
308+
[b.native_brep for b in breps_b],
309+
TOLERANCE
310+
)
311+
return [RhinoBrep.from_brep(brep) for brep in resulting_breps]
312+
214313
# ==============================================================================
215314
# Other Methods
216315
# ==============================================================================

src/compas_rhino/geometry/brep/face.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def _set_face(self, native_face):
4747
def data(self):
4848
boundary = self._loops[0].data
4949
holes = [loop.data for loop in self._loops[1:]]
50-
return {"boundary": boundary, "surface": self._surface.data, "holes": holes}
50+
surface = {"type": "nurbs"}
51+
surface.update(self.surface.data)
52+
return {"boundary": boundary, "surface": surface, "holes": holes}
5153

5254
@data.setter
5355
def data(self, value):

0 commit comments

Comments
 (0)