From a3ab8fb6c144a68172907b0f0f38084caf7b9b91 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 10 Apr 2025 19:30:10 +0200 Subject: [PATCH 01/12] added missing properties to RhinoBrep --- CHANGELOG.md | 10 +++ src/compas_rhino/geometry/brep/brep.py | 113 +++++++++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f34aa0fb03..21804d3f215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `compas.geometry.angle_vectors_projected`. * Added `compas.geometry.Brep.from_curves`. * Added `compas_rhino.geometry.RhinoBrep.from_curves`. +* Added missing property `centroid` in `compas_rhino.geometry.RhinoBrep`. +* Added missing property `curves` in `compas_rhino.geometry.RhinoBrep`. +* Added missing property `is_closed` in `compas_rhino.geometry.RhinoBrep`. +* Added missing property `is_compound` in `compas_rhino.geometry.RhinoBrep`. +* Added missing property `is_compoundsolid` in `compas_rhino.geometry.RhinoBrep`. +* Added missing property `is_orientable` in `compas_rhino.geometry.RhinoBrep`. +* Added missing property `is_surface` in `compas_rhino.geometry.RhinoBrep`. +* Added missing property `is_valid` in `compas_rhino.geometry.RhinoBrep`. +* Added missing property `orientation` in `compas_rhino.geometry.RhinoBrep`. +* Added missing property `surfaces` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index 0e8697b4a0c..4b39741a055 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -27,6 +27,8 @@ from compas_rhino.conversions import sphere_to_rhino from compas_rhino.conversions import transformation_to_rhino from compas_rhino.conversions import vector_to_rhino +from compas_rhino.geometry import RhinoNurbsCurve +from compas_rhino.geometry import RhinoNurbsSurface from .builder import _RhinoBrepBuilder from .edge import RhinoBrepEdge @@ -62,6 +64,36 @@ class RhinoBrep(Brep): The calculated area of this brep. volume : float, read-only The calculated volume of this brep. + centroid : :class:`compas.geometry.Point`, read-only + The calculated centroid of this brep. + curves : list[:class:`compas_rhino.geometry.RhinoNurbsCurve`], read-only + The list of curves which comprise this brep. + is_closed : bool, read-only + True if this brep is closed, False otherwise. + is_compound : bool, read-only + True if this brep is compound, False otherwise. + is_compoundsolid : bool, read-only + True if this brep is compound solid, False otherwise. + is_convex : bool, read-only + True if this brep is convex, False otherwise. + is_infinite : bool, read-only + True if this brep is infinite, False otherwise. + is_orientable : bool, read-only + True if this brep is orientable, False otherwise. + is_shell : bool, read-only + True if this brep is a shell, False otherwise. + is_surface : bool, read-only + True if this brep is a surface, False otherwise. + is_valid : bool, read-only + True if this brep is valid, False otherwise. + orientation : literal(:class:`~compas.geometry.BrepOrientation`), read-only + The orientation of this brep. One of: FORWARD, REVERSED, INTERNAL, EXTERNAL. + shells : list[:class:`compas_rhino.geometry.RhinoBrep`], read-only + The list of shells which comprise this brep. + solids : list[:class:`compas_rhino.geometry.RhinoBrep`], read-only + The list of solids which comprise this brep. + surfaces : list[:class:`compas_rhino.geometry.RhinoNurbsSurface`], read-only + The list of surfaces which comprise this brep. """ @@ -183,6 +215,87 @@ def volume(self): if self._brep: return self._brep.GetVolume() + @property + def centroid(self): + assert self._brep + centroid = Rhino.Geometry.AreaMassProperties.Compute(self._brep).Centroid + return Point(*centroid) + + @property + def curves(self): + assert self._brep + return [RhinoNurbsCurve.from_native(c.ToNurbsCurve()) for c in self._brep.Curves3D] + + @property + def is_closed(self): + assert self._brep + return self._brep.IsSolid + + @property + def is_compound(self): + # TODO: clarify. according to the internets compound brep is actually a container for several breps, not sure that's possible with a Rhino Brep. + return False + + @property + def is_compoundsolid(self): + # TODO: see above + return False + + @property + def is_convex(self): + raise NotImplementedError("Convexity check is not implemented for Rhino Breps.") + + @property + def is_infinite(self): + pass + + @property + def is_orientable(self): + assert self._brep + return self._brep.SolidOrientation in (Rhino.Geometry.BrepSolidOrientation.Inward, Rhino.Geometry.BrepSolidOrientation.Outward) + + @property + def is_shell(self): + # not sure how to get this one + raise NotImplementedError + + @property + def is_surface(self): + assert self._brep + return self._brep.IsSurface + + @property + def is_valid(self): + assert self._brep + return self.IsValid + + @property + def orientation(self): + assert self._brep + # TODO: align this with compas.geometry.BrepOrientation + return self._brep.SolidOrientation + + @property + def shells(self): + # TODO: can create shell from brep but have to specify which faces to eliminate in order to hollow out the brep, doesn't seem like the intention. + # TODO: is this about traversing a compound brep? + raise NotImplementedError("Shells are not implemented for Rhino Breps.") + + @property + def solids(self): + # TODO: same as above + raise NotImplementedError("Solids are not implemented for Rhino Breps.") + + @property + def surfaces(self): + assert self._brep + return [[RhinoNurbsSurface.from_native(s.ToNurbsSurface()) for s in self._brep.Surfaces]] + + @property + def type(self): + # TODO: seems like an OCC specific thinh, rename to occ_type and remove from interface? + raise NotImplementedError("Type is not implemented for Rhino Breps.") + # ============================================================================== # Constructors # ============================================================================== From ebdcb280f7db693f5a844b51298f73bb27fb0202 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 16 Apr 2025 11:38:30 +0200 Subject: [PATCH 02/12] implemented RhinoBrep.from_sweep --- CHANGELOG.md | 1 + src/compas/geometry/brep/brep.py | 4 +- src/compas_rhino/geometry/brep/__init__.py | 5 +++ src/compas_rhino/geometry/brep/brep.py | 47 ++++++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21804d3f215..b4cfeefcb6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added missing property `is_valid` in `compas_rhino.geometry.RhinoBrep`. * Added missing property `orientation` in `compas_rhino.geometry.RhinoBrep`. * Added missing property `surfaces` in `compas_rhino.geometry.RhinoBrep`. +* Added implementation for `Brep.from_sweep` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas/geometry/brep/brep.py b/src/compas/geometry/brep/brep.py index ffa07063f44..a82d4724947 100644 --- a/src/compas/geometry/brep/brep.py +++ b/src/compas/geometry/brep/brep.py @@ -569,7 +569,7 @@ def from_step(cls, filename): return from_step(filename) @classmethod - def from_sweep(cls, profile, path): + def from_sweep(cls, profile, path, *args, **kwargs): """Construct a BRep by sweeping a profile along a path. Parameters @@ -584,7 +584,7 @@ def from_sweep(cls, profile, path): :class:`compas.geometry.Brep` """ - return from_sweep(profile, path) + return from_sweep(profile, path, *args, **kwargs) @classmethod def from_torus(cls, torus): diff --git a/src/compas_rhino/geometry/brep/__init__.py b/src/compas_rhino/geometry/brep/__init__.py index 58dfd984b87..bdda1dd71ee 100644 --- a/src/compas_rhino/geometry/brep/__init__.py +++ b/src/compas_rhino/geometry/brep/__init__.py @@ -65,6 +65,11 @@ def from_step(*args, **kwargs): return RhinoBrep.from_step(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def from_sweep(*args, **kwargs): + return RhinoBrep.from_sweep(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def new_brep(*args, **kwargs): return object.__new__(RhinoBrep) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index 4b39741a055..94baa1de192 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -11,6 +11,7 @@ from compas.geometry import BrepFilletError from compas.geometry import BrepTrimmingError from compas.geometry import Frame +from compas.geometry import Line from compas.geometry import Plane from compas.geometry import Point from compas.geometry import Polyline @@ -19,6 +20,7 @@ from compas_rhino.conversions import curve_to_compas from compas_rhino.conversions import curve_to_rhino from compas_rhino.conversions import cylinder_to_rhino +from compas_rhino.conversions import line_to_rhino_curve from compas_rhino.conversions import mesh_to_compas from compas_rhino.conversions import mesh_to_rhino from compas_rhino.conversions import plane_to_rhino @@ -578,6 +580,51 @@ def from_step(cls, filepath): compas_rhino.objects.delete_object(guid) return cls.from_native(geometry) + @classmethod + def from_sweep(cls, profile, path, is_closed=False, tolerance=None): + """Construct one or more RhinoBrep(s) from a sweep operation. + + Parameters + ---------- + profile : :class:`compas.geometry.Curve` + Curve describing the cross-section of the surface created by the sweep operation. + path : :class:`compas.geometry.Curve` + Curve describing the edge of the sweep surface. The profile curve is sweeped along this curve. + is_closed : bool, optional + If True, the resulting surface will be closed, if possible. Defaults to False. + tolerance : float, optional + The precision to use for the operation. Defaults to `TOL.absolute`. + + Returns + ------- + list of :class:`compas_rhino.geometry.RhinoBrep` + + """ + tolerance = tolerance or TOL.absolute + if hasattr(profile, "native_curve"): + profile = curve_to_rhino(profile) + elif isinstance(profile, Polyline): + profile = polyline_to_rhino_curve(profile) + elif isinstance(profile, Line): + profile = line_to_rhino_curve(profile) + else: + raise TypeError("Unsupported profile type: {}".format(type(profile))) + + if hasattr(path, "native_curve"): + path = curve_to_rhino(path) + elif isinstance(path, Polyline): + path = polyline_to_rhino_curve(path) + elif isinstance(path, Line): + path = line_to_rhino_curve(path) + else: + raise TypeError("Unsupported path type: {}".format(type(path))) + + results = Rhino.Geometry.Brep.CreateFromSweep(path, profile, is_closed, tolerance) + if not results: + raise BrepError("Sweep operation ended with no result") + + return [cls.from_native(result) for result in results] + # ============================================================================== # Conversions # ============================================================================== From 13c76c7d6d31e559e82d206a934fde31af9e1d02 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 16 Apr 2025 13:33:25 +0200 Subject: [PATCH 03/12] added RhinoBrep.from_cone --- CHANGELOG.md | 1 + src/compas/geometry/brep/brep.py | 4 ++-- src/compas_rhino/geometry/brep/__init__.py | 5 +++++ src/compas_rhino/geometry/brep/brep.py | 18 ++++++++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4cfeefcb6a..46553d58100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added missing property `orientation` in `compas_rhino.geometry.RhinoBrep`. * Added missing property `surfaces` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_sweep` in `compas_rhino.geometry.RhinoBrep`. +* Added implementation for `Brep.from_cone` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas/geometry/brep/brep.py b/src/compas/geometry/brep/brep.py index a82d4724947..d209ba7e206 100644 --- a/src/compas/geometry/brep/brep.py +++ b/src/compas/geometry/brep/brep.py @@ -342,7 +342,7 @@ def from_breps(cls, breps): raise NotImplementedError @classmethod - def from_cone(cls, cone): + def from_cone(cls, cone, *args, **kwargs): """Construct a Brep from a COMPAS cone. Parameters @@ -354,7 +354,7 @@ def from_cone(cls, cone): :class:`compas.geometry.Brep` """ - return from_cone(cone) + return from_cone(cone, *args, **kwargs) @classmethod def from_curves(cls, curves): diff --git a/src/compas_rhino/geometry/brep/__init__.py b/src/compas_rhino/geometry/brep/__init__.py index bdda1dd71ee..426f7f94f85 100644 --- a/src/compas_rhino/geometry/brep/__init__.py +++ b/src/compas_rhino/geometry/brep/__init__.py @@ -25,6 +25,11 @@ def from_box(*args, **kwargs): return RhinoBrep.from_box(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def from_cone(*args, **kwargs): + return RhinoBrep.from_cone(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def from_cylinder(*args, **kwargs): return RhinoBrep.from_cylinder(*args, **kwargs) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index 94baa1de192..d6f45d968eb 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -17,6 +17,7 @@ from compas.geometry import Polyline from compas.tolerance import TOL from compas_rhino.conversions import box_to_rhino +from compas_rhino.conversions import cone_to_rhino from compas_rhino.conversions import curve_to_compas from compas_rhino.conversions import curve_to_rhino from compas_rhino.conversions import cylinder_to_rhino @@ -400,6 +401,23 @@ def from_box(cls, box): rhino_box = box_to_rhino(box) return cls.from_native(rhino_box.ToBrep()) + @classmethod + def from_cone(cls, cone, cap_bottom=True): + """Create a RhinoBrep from a cone. + + Parameters + ---------- + cone : :class:`compas.geometry.Cone` + The cone geometry of the brep. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoBrep` + + """ + rhino_cone = cone_to_rhino(cone) + return cls.from_native(rhino_cone.ToBrep(cap_bottom)) + @classmethod def from_cylinder(cls, cylinder): """Create a RhinoBrep from a box. From 1c1a21455fc37aa69d8e8894a124e62269efb3ad Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 16 Apr 2025 14:11:47 +0200 Subject: [PATCH 04/12] added RhinoBrep.from_plane --- CHANGELOG.md | 1 + src/compas_rhino/geometry/brep/__init__.py | 5 ++++ src/compas_rhino/geometry/brep/brep.py | 33 ++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46553d58100..100a53883fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added missing property `surfaces` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_sweep` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_cone` in `compas_rhino.geometry.RhinoBrep`. +* Added implementation for `Brep.from_plane` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas_rhino/geometry/brep/__init__.py b/src/compas_rhino/geometry/brep/__init__.py index 426f7f94f85..420b36b5340 100644 --- a/src/compas_rhino/geometry/brep/__init__.py +++ b/src/compas_rhino/geometry/brep/__init__.py @@ -60,6 +60,11 @@ def from_native(*args, **kwargs): return RhinoBrep.from_native(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def from_plane(*args, **kwargs): + return RhinoBrep.from_plane(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def from_sphere(*args, **kwargs): return RhinoBrep.from_sphere(*args, **kwargs) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index d6f45d968eb..45b35f3020c 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -21,6 +21,7 @@ from compas_rhino.conversions import curve_to_compas from compas_rhino.conversions import curve_to_rhino from compas_rhino.conversions import cylinder_to_rhino +from compas_rhino.conversions import frame_to_rhino_plane from compas_rhino.conversions import line_to_rhino_curve from compas_rhino.conversions import mesh_to_compas from compas_rhino.conversions import mesh_to_rhino @@ -560,6 +561,38 @@ def from_native(cls, rhino_brep): brep._brep = rhino_brep return brep + @classmethod + def from_plane(cls, plane, domain_u=(-1, +1), domain_v=(-1, +1)): + """Create a RhinoBrep from a plane. + + Parameters + ---------- + plane : :class:`compas.geometry.Plane` or :class:`compas.geometry.Frame` + The source plane. + domain_u : tuple of float, optional + The U domain of the plane. Defaults to (-1, +1). + domain_v : tuple of float, optional + The V domain of the plane. Defaults to (-1, +1). + + Notes + ----- + When using a Rhino Plane, to maintain the original orientation data + use :meth:`~compas_rhino.conversions.plane_to_compas_frame` and :meth:`~compas_rhino.conversions.frame_to_rhino_plane`. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoBrep` + + """ + if isinstance(plane, Frame): + rhino_plane = frame_to_rhino_plane(plane) + else: + rhino_plane = plane_to_rhino(plane) + u = Rhino.Geometry.Interval(domain_u[0], domain_u[1]) + v = Rhino.Geometry.Interval(domain_v[0], domain_v[1]) + surface = Rhino.Geometry.PlaneSurface(rhino_plane, u, v) + return cls.from_native(surface.ToBrep()) + @classmethod def from_sphere(cls, sphere): """Create a RhinoBrep from a sphere. From 7cb6b8ac55735ba9138a39cf10c8a1b0699a5b1c Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 16 Apr 2025 14:47:10 +0200 Subject: [PATCH 05/12] added RhinoBrep.from_brepfaces --- CHANGELOG.md | 1 + src/compas_rhino/geometry/brep/__init__.py | 5 +++++ src/compas_rhino/geometry/brep/brep.py | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 100a53883fa..3cfc550d26b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added implementation for `Brep.from_sweep` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_cone` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_plane` in `compas_rhino.geometry.RhinoBrep`. +* Added implementation for `Brep.from_brepfaces` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas_rhino/geometry/brep/__init__.py b/src/compas_rhino/geometry/brep/__init__.py index 420b36b5340..1e79523ec69 100644 --- a/src/compas_rhino/geometry/brep/__init__.py +++ b/src/compas_rhino/geometry/brep/__init__.py @@ -25,6 +25,11 @@ def from_box(*args, **kwargs): return RhinoBrep.from_box(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def from_brepfaces(*args, **kwargs): + return RhinoBrep.from_brepfaces(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def from_cone(*args, **kwargs): return RhinoBrep.from_cone(*args, **kwargs) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index 45b35f3020c..97224b9f261 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -402,6 +402,24 @@ def from_box(cls, box): rhino_box = box_to_rhino(box) return cls.from_native(rhino_box.ToBrep()) + @classmethod + def from_brepfaces(cls, faces): + """Create a Brep from a list of Brep faces forming an open or closed shell. + + Parameters + ---------- + faces : list[:class:`compas.geometry.BrepFace`] + + Returns + ------- + :class:`compas.geometry.Brep` + + """ + brep = Rhino.Geometry.Brep() + for face in faces: + brep.Faces.Add(face.native_face.UnderlyingSurface()) + return cls.from_native(brep) + @classmethod def from_cone(cls, cone, cap_bottom=True): """Create a RhinoBrep from a cone. From 242fe6e307e7452542a272b1b520838ae1935662 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 16 Apr 2025 15:04:10 +0200 Subject: [PATCH 06/12] added RhinoBrep.from_breps --- CHANGELOG.md | 1 + src/compas/geometry/brep/__init__.py | 5 +++++ src/compas/geometry/brep/brep.py | 9 +++++---- src/compas_rhino/geometry/brep/__init__.py | 5 +++++ src/compas_rhino/geometry/brep/brep.py | 18 ++++++++++++++++++ 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cfc550d26b..b356f6f8112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added implementation for `Brep.from_cone` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_plane` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_brepfaces` in `compas_rhino.geometry.RhinoBrep`. +* Added implementation for `Brep.from_breps` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas/geometry/brep/__init__.py b/src/compas/geometry/brep/__init__.py index 5abec46e466..0a5d25b52c5 100644 --- a/src/compas/geometry/brep/__init__.py +++ b/src/compas/geometry/brep/__init__.py @@ -27,6 +27,11 @@ def from_brepfaces(*args, **kwargs): raise PluginNotInstalledError +@pluggable(category="factories") +def from_breps(*args, **kwargs): + raise PluginNotInstalledError + + @pluggable(category="factories") def from_cone(*args, **kwargs): raise PluginNotInstalledError diff --git a/src/compas/geometry/brep/brep.py b/src/compas/geometry/brep/brep.py index d209ba7e206..7553cfc8dd2 100644 --- a/src/compas/geometry/brep/brep.py +++ b/src/compas/geometry/brep/brep.py @@ -5,6 +5,7 @@ from . import from_boolean_union from . import from_box from . import from_brepfaces +from . import from_breps from . import from_cone from . import from_curves from . import from_cylinder @@ -327,19 +328,19 @@ def from_brepfaces(cls, faces): return from_brepfaces(faces) @classmethod - def from_breps(cls, breps): + def from_breps(cls, breps, *args, **kwargs): """Construct one compound Brep from a list of other Breps. Parameters ---------- - breps : list[:class:`compas.geometry.Brep`] + breps : list of :class:`compas.geometry.Brep` Returns ------- - :class:`compas.geometry.Brep` + list of :class:`compas.geometry.Brep` """ - raise NotImplementedError + return from_breps(breps, *args, **kwargs) @classmethod def from_cone(cls, cone, *args, **kwargs): diff --git a/src/compas_rhino/geometry/brep/__init__.py b/src/compas_rhino/geometry/brep/__init__.py index 1e79523ec69..bd79156b22e 100644 --- a/src/compas_rhino/geometry/brep/__init__.py +++ b/src/compas_rhino/geometry/brep/__init__.py @@ -30,6 +30,11 @@ def from_brepfaces(*args, **kwargs): return RhinoBrep.from_brepfaces(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def from_breps(*args, **kwargs): + return RhinoBrep.from_breps(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def from_cone(*args, **kwargs): return RhinoBrep.from_cone(*args, **kwargs) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index 97224b9f261..65b37b87565 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -420,6 +420,24 @@ def from_brepfaces(cls, faces): brep.Faces.Add(face.native_face.UnderlyingSurface()) return cls.from_native(brep) + @classmethod + def from_breps(cls, breps, tolerance=None): + """Joins the breps at any overlapping edges to form as few as possible resulting breps. There may be more than one brep in the result array. + + Parameters + ---------- + breps : list of :class:`compas.geometry.Brep` + + Returns + ------- + list of :class:`compas.geometry.Brep` + + """ + tolerance = tolerance or TOL + rhino_breps = [b.native_brep for b in breps] + resulting_breps = Rhino.Geometry.Brep.JoinBreps(rhino_breps, tolerance.absolute, tolerance.angular) + return [cls.from_native(brep) for brep in resulting_breps] + @classmethod def from_cone(cls, cone, cap_bottom=True): """Create a RhinoBrep from a cone. From df0dd94617ad2143b0c2e694acebc0af2154b8c6 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Wed, 16 Apr 2025 15:11:19 +0200 Subject: [PATCH 07/12] added RhinoBrep.from_torus --- CHANGELOG.md | 1 + src/compas_rhino/geometry/brep/__init__.py | 5 +++++ src/compas_rhino/geometry/brep/brep.py | 17 +++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b356f6f8112..b58805e454b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added implementation for `Brep.from_plane` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_brepfaces` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_breps` in `compas_rhino.geometry.RhinoBrep`. +* Added implementation for `Brep.from_torus` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas_rhino/geometry/brep/__init__.py b/src/compas_rhino/geometry/brep/__init__.py index bd79156b22e..7296b3dce06 100644 --- a/src/compas_rhino/geometry/brep/__init__.py +++ b/src/compas_rhino/geometry/brep/__init__.py @@ -90,6 +90,11 @@ def from_sweep(*args, **kwargs): return RhinoBrep.from_sweep(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def from_torus(*args, **kwargs): + return RhinoBrep.from_torus(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def new_brep(*args, **kwargs): return object.__new__(RhinoBrep) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index 65b37b87565..8c61faa03e6 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -29,6 +29,7 @@ from compas_rhino.conversions import point_to_rhino from compas_rhino.conversions import polyline_to_rhino_curve from compas_rhino.conversions import sphere_to_rhino +from compas_rhino.conversions import torus_to_rhino from compas_rhino.conversions import transformation_to_rhino from compas_rhino.conversions import vector_to_rhino from compas_rhino.geometry import RhinoNurbsCurve @@ -712,6 +713,22 @@ def from_sweep(cls, profile, path, is_closed=False, tolerance=None): return [cls.from_native(result) for result in results] + @classmethod + def from_torus(cls, torus): + """Construct a RhinoBrep from a COMPAS torus. + + Parameters + ---------- + torus : :class:`compas.geometry.Torus` + + Returns + ------- + :class:`compas.geometry.BRep` + + """ + rhino_torus = torus_to_rhino(torus) + return cls.from_native(rhino_torus.ToBrep()) + # ============================================================================== # Conversions # ============================================================================== From 08e922df22c6f7408964e009d10664533fd925f6 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 17 Apr 2025 15:10:22 +0200 Subject: [PATCH 08/12] added RhinoBrep.from_polygons --- CHANGELOG.md | 1 + src/compas/geometry/brep/brep.py | 4 ++-- src/compas_rhino/geometry/brep/__init__.py | 5 ++++ src/compas_rhino/geometry/brep/brep.py | 28 +++++++++++++++++++--- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b58805e454b..380bc8f2f52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added implementation for `Brep.from_brepfaces` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_breps` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_torus` in `compas_rhino.geometry.RhinoBrep`. +* Added implementation for `Brep.from_polygons` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas/geometry/brep/brep.py b/src/compas/geometry/brep/brep.py index 7553cfc8dd2..65eabaf1532 100644 --- a/src/compas/geometry/brep/brep.py +++ b/src/compas/geometry/brep/brep.py @@ -525,7 +525,7 @@ def from_planes(cls, planes): return from_planes(planes) @classmethod - def from_polygons(cls, polygons): + def from_polygons(cls, polygons, *args, **kwargs): """Construct a Brep from a set of polygons. Parameters @@ -537,7 +537,7 @@ def from_polygons(cls, polygons): :class:`compas.geometry.Brep` """ - return from_polygons(polygons) + return from_polygons(polygons, *args, **kwargs) @classmethod def from_sphere(cls, sphere): diff --git a/src/compas_rhino/geometry/brep/__init__.py b/src/compas_rhino/geometry/brep/__init__.py index 7296b3dce06..ad21279f378 100644 --- a/src/compas_rhino/geometry/brep/__init__.py +++ b/src/compas_rhino/geometry/brep/__init__.py @@ -75,6 +75,11 @@ def from_plane(*args, **kwargs): return RhinoBrep.from_plane(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def from_polygons(*args, **kwargs): + return RhinoBrep.from_polygons(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def from_sphere(*args, **kwargs): return RhinoBrep.from_sphere(*args, **kwargs) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index 8c61faa03e6..66ff5629f7d 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -474,7 +474,7 @@ def from_cylinder(cls, cylinder): return cls.from_native(rhino_cylinder.ToBrep(True, True)) @classmethod - def from_curves(cls, curves): + def from_curves(cls, curves, tolerance=None): """Create a RhinoBreps from a list of planar face boundary curves. Parameters @@ -487,6 +487,7 @@ def from_curves(cls, curves): list of :class:`~compas_rhino.geometry.RhinoBrep` """ + tolerance = tolerance or TOL.absolute if not isinstance(curves, list): curves = [curves] faces = [] @@ -495,13 +496,13 @@ def from_curves(cls, curves): rhino_curve = polyline_to_rhino_curve(curve) else: rhino_curve = curve_to_rhino(curve) - face = Rhino.Geometry.Brep.CreatePlanarBreps(rhino_curve, TOL.absolute) + face = Rhino.Geometry.Brep.CreatePlanarBreps(rhino_curve, tolerance) if face is None: raise BrepError("Failed to create face from curve: {} ".format(curve)) if len(face) > 1: raise BrepError("Failed to create single face from curve: {} ".format(curve)) faces.append(face[0]) - rhino_brep = Rhino.Geometry.Brep.JoinBreps(faces, TOL.absolute) + rhino_brep = Rhino.Geometry.Brep.JoinBreps(faces, tolerance) if rhino_brep is None: raise BrepError("Failed to create Brep from faces: {} ".format(faces)) return [cls.from_native(brep) for brep in rhino_brep] @@ -630,6 +631,27 @@ def from_plane(cls, plane, domain_u=(-1, +1), domain_v=(-1, +1)): surface = Rhino.Geometry.PlaneSurface(rhino_plane, u, v) return cls.from_native(surface.ToBrep()) + @classmethod + def from_polygons(cls, polygons, tolerance=None, *args, **kwargs): + """Create a RhinoBrep from a list of polygons. + + Parameters + ---------- + polygons : list of :class:`compas.geometry.Polygon` + The source polygons. + + Returns + ------- + list of :class:`compas_rhino.geometry.RhinoBrep` + + """ + tolerance = tolerance or TOL.absolute + polylines = [] + for polygon in polygons: + points = polygon.points + [polygon.points[0]] # make a closed polyline from the polygon + polylines.append(Polyline(points=[*points])) + return cls.from_curves(polylines, tolerance) + @classmethod def from_sphere(cls, sphere): """Create a RhinoBrep from a sphere. From f6097d756f05ac3fd0aa9326e426fcb1c25ab4c3 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 17 Apr 2025 16:04:20 +0200 Subject: [PATCH 09/12] added RhinoBrep.from_pipe --- CHANGELOG.md | 1 + src/compas/geometry/brep/brep.py | 9 ++--- src/compas_rhino/geometry/brep/__init__.py | 5 +++ src/compas_rhino/geometry/brep/brep.py | 46 ++++++++++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 380bc8f2f52..75c32bcab13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added implementation for `Brep.from_breps` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_torus` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_polygons` in `compas_rhino.geometry.RhinoBrep`. +* Added implementation for `Brep.from_pipe` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas/geometry/brep/brep.py b/src/compas/geometry/brep/brep.py index 65eabaf1532..85fc6c66924 100644 --- a/src/compas/geometry/brep/brep.py +++ b/src/compas/geometry/brep/brep.py @@ -468,8 +468,8 @@ def from_native(cls, native_brep): return from_native(native_brep) @classmethod - def from_pipe(cls, curve, radius, thickness=None): - """Construct a Brep by extruding a closed curve along a path curve. + def from_pipe(cls, path, radius, *args, **kwargs): + """Construct a Brep by extruding a circle curve along the path curve. Parameters ---------- @@ -477,16 +477,13 @@ def from_pipe(cls, curve, radius, thickness=None): The curve to extrude radius : float The radius of the pipe. - thickness : float, optional - The thickness of the pipe. - The thickness should be smaller than the radius. Returns ------- :class:`compas.geometry.Brep` """ - return from_pipe(curve, radius, thickness=thickness) + return from_pipe(path, radius, *args, **kwargs) @classmethod def from_plane(cls, plane, domain_u=(-1, +1), domain_v=(-1, +1)): diff --git a/src/compas_rhino/geometry/brep/__init__.py b/src/compas_rhino/geometry/brep/__init__.py index ad21279f378..4e7284c0d70 100644 --- a/src/compas_rhino/geometry/brep/__init__.py +++ b/src/compas_rhino/geometry/brep/__init__.py @@ -80,6 +80,11 @@ def from_polygons(*args, **kwargs): return RhinoBrep.from_polygons(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def from_pipe(*args, **kwargs): + return RhinoBrep.from_pipe(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def from_sphere(*args, **kwargs): return RhinoBrep.from_sphere(*args, **kwargs) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index 66ff5629f7d..ab4142de2f5 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -599,6 +599,52 @@ def from_native(cls, rhino_brep): brep._brep = rhino_brep return brep + @classmethod + def from_pipe(cls, path, radius, cap_mode="none", tolerance=None, *args, **kwargs): + """Construct a Brep by extruding a circle curve along the path curve. + + Parameters + ---------- + curve : :class:`compas.geometry.Curve` + The curve to extrude + radius : float + The radius of the pipe. + cap_mode : literal('none', 'flat', 'round'), optional + The type of end caps to create. Defaults to 'none'. + tolerance : :class:`~compas.tolerance.Tolerance`, optional + A Tolerance instance to use for the operation. Defaults to `TOL`. + + Returns + ------- + :class:`compas.geometry.Brep` + + """ + tolerance = tolerance or TOL + + if cap_mode == "none": + cap_mode = Rhino.Geometry.PipeCapMode.NONE + elif cap_mode == "flat": + cap_mode = Rhino.Geometry.PipeCapMode.Flat + elif cap_mode == "round": + cap_mode = Rhino.Geometry.PipeCapMode.Round + else: + raise ValueError("Invalid cap_ends value. Must be 'none', 'flat' or 'round'.") + + if hasattr(path, "native_curve"): + path = curve_to_rhino(path) + elif isinstance(path, Polyline): + path = polyline_to_rhino_curve(path) + elif isinstance(path, Line): + path = line_to_rhino_curve(path) + else: + raise TypeError("Unsupported path curve type: {}".format(type(path))) + + result = Rhino.Geometry.Brep.CreatePipe(path, radius, False, cap_mode, True, tolerance.absolute, tolerance.angular) + if result is None: + raise BrepError("Failed to create pipe from curve: {} and radius: {}".format(path, radius)) + + return [cls.from_native(brep) for brep in result] + @classmethod def from_plane(cls, plane, domain_u=(-1, +1), domain_v=(-1, +1)): """Create a RhinoBrep from a plane. From 3aa022ca1d054d30a93884ccaf2da61690e24e78 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 17 Apr 2025 16:48:59 +0200 Subject: [PATCH 10/12] added RhinoBrep.from_iges --- CHANGELOG.md | 1 + src/compas_rhino/geometry/brep/__init__.py | 5 ++++ src/compas_rhino/geometry/brep/brep.py | 28 ++++++++++++++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c32bcab13..002b051ba2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added implementation for `Brep.from_torus` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_polygons` in `compas_rhino.geometry.RhinoBrep`. * Added implementation for `Brep.from_pipe` in `compas_rhino.geometry.RhinoBrep`. +* Added implementation for `Brep.from_iges` in `compas_rhino.geometry.RhinoBrep`. ### Changed diff --git a/src/compas_rhino/geometry/brep/__init__.py b/src/compas_rhino/geometry/brep/__init__.py index 4e7284c0d70..ef8c7e99500 100644 --- a/src/compas_rhino/geometry/brep/__init__.py +++ b/src/compas_rhino/geometry/brep/__init__.py @@ -55,6 +55,11 @@ def from_curves(*args, **kwargs): return RhinoBrep.from_curves(*args, **kwargs) +@plugin(category="factories", requires=["Rhino"]) +def from_iges(*args, **kwargs): + return RhinoBrep.from_iges(*args, **kwargs) + + @plugin(category="factories", requires=["Rhino"]) def from_loft(*args, **kwargs): return RhinoBrep.from_loft(*args, **kwargs) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index ab4142de2f5..4660ce7ab5f 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -539,6 +539,24 @@ def from_extrusion(cls, curve, vector, cap_ends=True): rhino_brep = capped return cls.from_native(rhino_brep) + @classmethod + def from_iges(cls, filepath): + """Construct a RhinoBrep from a IGES file. + + Parameters + ---------- + filepath : str + The path to the step file. + + Returns + ------- + :class:`compas_rhino.geometry.RhinoBrep` + + """ + if not filepath.endswith(".igs"): + raise ValueError("Expected file with .igs extension") + return cls._import_brep_from_file(filepath) + @classmethod def from_loft(cls, curves): """Construct a Brep by lofting a set of curves. @@ -729,12 +747,18 @@ def from_step(cls, filepath): :class:`compas_rhino.geometry.RhinoBrep` """ + if not not filepath.endswith(".step"): + raise ValueError("Expected file with .igs extension") + return cls._import_brep_from_file(filepath) + + @staticmethod + def _import_brep_from_file(filepath): rs.Command('_-Import "' + filepath + '" _Enter', False) - guid = rs.LastCreatedObjects()[0] + guid = rs.LastCreatedObjects()[0] # this fails, could be Rhino bug obj = compas_rhino.objects.find_object(guid) geometry = obj.Geometry.Duplicate() compas_rhino.objects.delete_object(guid) - return cls.from_native(geometry) + return RhinoBrep.from_native(geometry) @classmethod def from_sweep(cls, profile, path, is_closed=False, tolerance=None): From b472018c622d7f82c9000139297acf81ced0aaa3 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 17 Apr 2025 16:54:41 +0200 Subject: [PATCH 11/12] removed double negation --- src/compas_rhino/geometry/brep/brep.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index 4660ce7ab5f..df76ccbce9a 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -747,7 +747,7 @@ def from_step(cls, filepath): :class:`compas_rhino.geometry.RhinoBrep` """ - if not not filepath.endswith(".step"): + if not filepath.endswith(".step"): raise ValueError("Expected file with .igs extension") return cls._import_brep_from_file(filepath) From 3971f742ee1bd0124668eddf8f96bec1ed5c0434 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 17 Apr 2025 16:57:10 +0200 Subject: [PATCH 12/12] don't fail silently --- src/compas_rhino/geometry/brep/brep.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compas_rhino/geometry/brep/brep.py b/src/compas_rhino/geometry/brep/brep.py index df76ccbce9a..fef60c01bff 100644 --- a/src/compas_rhino/geometry/brep/brep.py +++ b/src/compas_rhino/geometry/brep/brep.py @@ -252,7 +252,8 @@ def is_convex(self): @property def is_infinite(self): - pass + # TODO: what does this exactly mean? couldn't find in the Rhino API + raise NotImplementedError @property def is_orientable(self):