Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Added `compas.geometry.Brep.cap_planar_holes`.
* Added `compas_rhino.geometry.RhinoBrep.cap_planar_holes`.
* Added `compas.geometry.angle_vectors_projected`.
* Added `compas.geometry.Brep.from_curves`.
* Added `compas_rhino.geometry.RhinoBrep.from_curves`.

### Changed

Expand Down
4 changes: 2 additions & 2 deletions src/compas/geometry/brep/brep.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,15 +358,15 @@ def from_cone(cls, cone):

@classmethod
def from_curves(cls, curves):
"""Construct a Brep from a set of curves.
"""Construct a Brep from a set of closed, planar curves.

Parameters
----------
curves : list[:class:`compas.geometry.NurbsCurve`]

Returns
-------
:class:`compas.geometry.Brep`
list [:class:`compas.geometry.Brep`]

"""
return from_curves(curves)
Expand Down
5 changes: 5 additions & 0 deletions src/compas_rhino/geometry/brep/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ def from_extrusion(*args, **kwargs):
return RhinoBrep.from_extrusion(*args, **kwargs)


@plugin(category="factories", requires=["Rhino"])
def from_curves(*args, **kwargs):
return RhinoBrep.from_curves(*args, **kwargs)


@plugin(category="factories", requires=["Rhino"])
def from_loft(*args, **kwargs):
return RhinoBrep.from_loft(*args, **kwargs)
Expand Down
33 changes: 33 additions & 0 deletions src/compas_rhino/geometry/brep/brep.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,39 @@ def from_cylinder(cls, cylinder):
rhino_cylinder = cylinder_to_rhino(cylinder)
return cls.from_native(rhino_cylinder.ToBrep(True, True))

@classmethod
def from_curves(cls, curves):
"""Create a RhinoBreps from a list of planar face boundary curves.

Parameters
----------
curves : list of :class:`~compas.geometry.Curve` or :class:`~compas.geometry.Polyline`
The planar curves that make up the face borders of brep faces.

Returns
-------
list of :class:`~compas_rhino.geometry.RhinoBrep`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that CreatePlanarBreps returns a list of Breps but I imagined this function creating a solid from the outline of multiple faces of the (eventually) same brep. I'm also wondering because of the original docstring specifies a single Brep as the return type. There's currently no OCC implementation so nothing to compare to, but I'm simply not sure what's the intention here.

Could you maybe provide a use-case for this method that illustrates what we should be able to make with it?

Copy link
Contributor Author

@obucklin obucklin Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that CreatePlanarBreps returns a list of Breps but I imagined this function creating a solid from the outline of multiple faces of the (eventually) same brep.

this is the general use case. however if not all of the faces are joinable, how do you decide which ones to get rid of?

I'm also wondering because of the original docstring specifies a single Brep as the return type. There's currently no OCC implementation so nothing to compare to, but I'm simply not sure what's the intention here.

I can either update the docstring to return a list of Breps, or I can return None if the faces don't join into one single Brep. I would typically go for the former and have the user decide if they only accept one single Brep or if they use multiple Breps.

Could you maybe provide a use-case for this method that illustrates what we should be able to make with it?

I was developing this to build a brep of a timber plate in the way that the compas_model.Plate._compute_geometry() does it, that is by building faces from polyline points. That type builds a mesh from vertices and I want to build a Brep, obviously. sample code looks like this:

        for i in range(len(self.outline_a)-1):
            a = self.outline_a.points[i]
            b = self.outline_a.points[i+1]
            c = self.outline_b.points[i+1]
            d = self.outline_b.points[i]
            curves.append(NurbsCurve.from_points([a, b, c, d, a], degree=1))
        plate_geo = Brep.from_curves(curves)
        if len(plate_geo) != 1:
            raise ValueError("The curves could not be joined into a single Brep.")  
        else:   
            plate_geo = plate_geo[0] 

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, fair enough!


"""
if not isinstance(curves, list):
curves = [curves]
faces = []
for curve in curves:
if isinstance(curve, Polyline):
rhino_curve = polyline_to_rhino_curve(curve)
else:
rhino_curve = curve_to_rhino(curve)
face = Rhino.Geometry.Brep.CreatePlanarBreps(rhino_curve, TOL.absolute)
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)
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]

@classmethod
def from_extrusion(cls, curve, vector, cap_ends=True):
"""Create a RhinoBrep from an extrusion.
Expand Down
Loading