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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Added conversion function `frame_to_rhino_plane` to `compas_rhino.conversions`.
* Added `RhinoSurface.from_frame` to `compas_rhino.geometry`.
* Added representation for trims with `compas.geometry.BrepTrim`.

### Changed

### Removed
Expand All @@ -24,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* Fixed strange point values in RhinoNurbsCurve caused by conversion `ControlPoint` to COMPAS instead of `ControlPoint.Location`.
* Fixed flipped order of NURBS point count values when creating RhinoNurbsSurface from parameters.
* Changed serialization format and reconstruction procedure of `RhinoBrep`.

### Removed

Expand Down
6 changes: 6 additions & 0 deletions src/compas/geometry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@
BrepEdge
BrepLoop
BrepFace
BrepTrim
BrepTrimIsoStatus

BrepType
BrepOrientation
Expand Down Expand Up @@ -892,6 +894,8 @@
BrepFace,
BrepLoop,
BrepEdge,
BrepTrim,
BrepTrimIsoStatus,
BrepType,
BrepOrientation,
BrepError,
Expand Down Expand Up @@ -1202,6 +1206,8 @@
"BrepEdge",
"BrepVertex",
"BrepFace",
"BrepTrim",
"BrepTrimIsoStatus",
"BrepType",
"BrepOrientation",
"BrepError",
Expand Down
4 changes: 4 additions & 0 deletions src/compas/geometry/brep/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from .loop import BrepLoop
from .face import BrepFace
from .vertex import BrepVertex
from .trim import BrepTrim
from .trim import BrepTrimIsoStatus


class BrepError(Exception):
Expand All @@ -31,6 +33,8 @@ class BrepTrimmingError(BrepError):
"BrepLoop",
"BrepFace",
"BrepVertex",
"BrepTrim",
"BrepTrimIsoStatus",
"BrepOrientation",
"BrepType",
"BrepError",
Expand Down
6 changes: 6 additions & 0 deletions src/compas/geometry/brep/brep.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ class Brep(Geometry):
The vertices of the Brep.
edges : list[:class:`~compas.geometry.BrepEdge`], read-only
The edges of the Brep.
trims : list[:class:`~compas.geometry.BrepTrim`], read-only
The trims of the Brep.
loops : list[:class:`~compas.geometry.BrepLoop`], read-only
The loops of the Brep.
faces : list[:class:`~compas.geometry.BrepFace`], read-only
Expand Down Expand Up @@ -314,6 +316,10 @@ def vertices(self):
def edges(self):
raise NotImplementedError

@property
def trims(self):
raise NotImplementedError

@property
def loops(self):
raise NotImplementedError
Expand Down
40 changes: 40 additions & 0 deletions src/compas/geometry/brep/trim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from compas.data import Data


class BrepTrimIsoStatus(object):
"""An enumeration of isoparametric curve direction on the surface."""

NONE = 0
X = 1
Y = 2
WEST = 3
SOUTH = 4
EAST = 5
NORTH = 6


class BrepTrim(Data):
"""An interface for a Brep Trim

Attributes
----------
curve : :class:`~compas.geometry.NurbsCurve`, read_only
Returns the geometry for this trim as a 2d curve.
iso_status : literal(NONE|X|Y|WEST|SOUTH|EAST|NORTH)
The isoparametric curve direction on the surface.
is_reversed : bool
True if this trim is reversed from its associated edge curve and False otherwise.

"""

@property
def curve(self):
raise NotImplementedError

@property
def iso_status(self):
raise NotImplementedError

@property
def is_reversed(self):
raise NotImplementedError
3 changes: 3 additions & 0 deletions src/compas_rhino/conversions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
line_to_rhino
plane_to_rhino
frame_to_rhino
frame_to_rhino_plane
circle_to_rhino
ellipse_to_rhino
polyline_to_rhino
Expand Down Expand Up @@ -134,6 +135,7 @@
line_to_rhino,
plane_to_rhino,
frame_to_rhino,
frame_to_rhino_plane,
circle_to_rhino,
ellipse_to_rhino,
polyline_to_rhino,
Expand Down Expand Up @@ -196,6 +198,7 @@
"line_to_rhino",
"plane_to_rhino",
"frame_to_rhino",
"frame_to_rhino_plane",
"circle_to_rhino",
"ellipse_to_rhino",
"polyline_to_rhino",
Expand Down
15 changes: 15 additions & 0 deletions src/compas_rhino/conversions/_primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,21 @@ def plane_to_compas_frame(plane):
)


def frame_to_rhino_plane(frame):
"""Convert a COMPAS frame to a Rhino plane.

Parameters
----------
frame : :class:`~compas.geometry.Frame`

Returns
-------
:rhino:`Rhino.Geometry.Plane`

"""
return RhinoPlane(point_to_rhino(frame.point), vector_to_rhino(frame.xaxis), vector_to_rhino(frame.yaxis))


def frame_to_rhino(frame):
"""Convert a COMPAS frame to a Rhino plane.

Expand Down
14 changes: 12 additions & 2 deletions src/compas_rhino/geometry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
RhinoBrepEdge
RhinoBrepFace
RhinoBrepLoop

RhinoBrepTrim
RhinoBrepBuilder
RhinoFaceBuilder
RhinoLoopBuilder

Plugins
=======
Expand Down Expand Up @@ -91,7 +94,10 @@
from .brep import RhinoBrepVertex
from .brep import RhinoBrepFace
from .brep import RhinoBrepEdge

from .brep import RhinoBrepTrim
from .brep import RhinoBrepBuilder
from .brep import RhinoFaceBuilder
from .brep import RhinoLoopBuilder

__all__ = [
"RhinoGeometry",
Expand All @@ -116,4 +122,8 @@
"RhinoBrepEdge",
"RhinoBrepFace",
"RhinoBrepLoop",
"RhinoBrepTrim",
"RhinoBrepBuilder",
"RhinoFaceBuilder",
"RhinoLoopBuilder",
]
8 changes: 8 additions & 0 deletions src/compas_rhino/geometry/brep/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from .edge import RhinoBrepEdge
from .vertex import RhinoBrepVertex
from .loop import RhinoBrepLoop
from .trim import RhinoBrepTrim
from .builder import RhinoBrepBuilder
from .builder import RhinoFaceBuilder
from .builder import RhinoLoopBuilder

import Rhino

Expand All @@ -15,6 +19,10 @@
"RhinoBrepEdge",
"RhinoBrepLoop",
"RhinoBrepFace",
"RhinoBrepTrim",
"RhinoBrepBuilder",
"RhinoFaceBuilder",
"RhinoLoopBuilder",
"new_brep",
"from_native",
"from_box",
Expand Down
117 changes: 21 additions & 96 deletions src/compas_rhino/geometry/brep/brep.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
from compas.geometry import Frame
from compas.geometry import Brep
from compas.geometry import BrepInvalidError
from compas.geometry import BrepTrimmingError
from compas.geometry import Plane

from compas_rhino.conversions import box_to_rhino
from compas_rhino.conversions import point_to_rhino
from compas_rhino.conversions import xform_to_rhino
from compas_rhino.conversions import frame_to_rhino
from compas_rhino.conversions import cylinder_to_rhino

import Rhino

from .builder import RhinoBrepBuilder
from .face import RhinoBrepFace
from .edge import RhinoBrepEdge
from .vertex import RhinoBrepVertex
Expand All @@ -36,6 +35,8 @@ class RhinoBrep(Brep):
The list of vertex geometries as points in 3D space.
edges : list[:class:`~compas_rhino.geometry.RhinoBrepEdge`], read-only
The list of edges which comprise this brep.
trims : list[:class:`~compas_rhino.geometry.RhinoBrepTrim`], read-only
The list of trims which comprise this brep.
loops : list[:class:`~compas_rhino.geometry.RhinoBrepLoop`], read-only
The list of loops which comprise this brep.
faces : list[:class:`~compas_rhino.geometry.RhinoBrepFace`], read-only
Expand Down Expand Up @@ -70,18 +71,22 @@ def __init__(self, brep=None):

@property
def data(self):
faces = []
for face in self.faces:
faces.append(face.data)
return {"faces": faces}
return {
"vertices": [v.data for v in self.vertices],
"edges": [e.data for e in self.edges],
"faces": [f.data for f in self.faces],
}

@data.setter
def data(self, data):
faces = []
for facedata in data["faces"]:
face = RhinoBrepFace.from_data(facedata)
faces.append(face)
self._create_native_brep(faces)
builder = RhinoBrepBuilder()
for v_data in data["vertices"]:
RhinoBrepVertex.from_data(v_data, builder)
for e_data in data["edges"]:
RhinoBrepEdge.from_data(e_data, builder)
for f_data in data["faces"]:
RhinoBrepFace.from_data(f_data, builder)
self._brep = builder.result
Comment on lines +82 to +89
Copy link
Member

Choose a reason for hiding this comment

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

would it not make more sense to add stuff to the builder rather than to modify the signature of all the constructors to send the builder around?

Copy link
Member Author

@chenkasirer chenkasirer Dec 6, 2022

Choose a reason for hiding this comment

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

The builder serves:

  • keeps the reconstruction logic in a single place
  • keeps the reconstruction logic separate from the de-serialization
  • makes the reconstruction context (i.e. Brep) available to each of the components while avoiding global state
  • enables the de-serialized elements to hold the native Rhino.Geometry type immediately after de-serialization.

BrepBuilder is the main context, it essentially wraps around the newly created Rhino.Geometry.Brep and holds it until it's properly built.

Since Face, Loop and Trim are nested in each other:

BrepFaceBuilder wraps around Rhino.Geometry.BrepFace and handed over to the loops so that they can add themselves to the face.
BrepLoopBuilder wraps around Rhino.Geometry.BrepLoop and handed over to the trims so that they can add themselves to the loop.

The result property of the builders gives access to the newly built native element once it's ready. BrepLoopBuilder.result returns the Rhino.Geometry.BrepLoop after all the trims have been added to it. BrepFaceBuilder.result returns the Rhino.Geometry.BrepFace after all the loops have been added to it. BrepBuilder.result returns the Rhino.Geometry.Brep after all the vertices, edges and faces have been added to it.


# ==============================================================================
# Properties
Expand All @@ -102,6 +107,11 @@ def points(self):

@property
def edges(self):
if self._brep:
return [RhinoBrepEdge(edge) for edge in self._brep.Edges]

@property
def trims(self):
if self._brep:
return [RhinoBrepEdge(trim) for trim in self._brep.Trims]

Expand Down Expand Up @@ -326,88 +336,3 @@ def split(self, cutter):
"""
resulting_breps = self._brep.Split(cutter.native_brep, TOLERANCE)
return [RhinoBrep.from_native(brep) for brep in resulting_breps]

# ==============================================================================
# Other Methods
# ==============================================================================

def _create_native_brep(self, faces):
# Source: https://github.com/mcneel/rhino-developer-samples/blob/3179a8386a64602ee670cc832c77c561d1b0944b/rhinocommon/cs/SampleCsCommands/SampleCsTrimmedPlane.cs
# Things need to be defined in a valid brep:
# 1- Vertices
# 2- 3D Curves (geometry)
# 3- Edges (topology - reference curve geometry)
# 4- Surfaces (geometry)
# 5- Faces (topology - reference surface geometry)
# 6- Loops (2D parameter space of faces)
# 4- Trims and 2D curves (2D parameter space of edges)
self._brep = Rhino.Geometry.Brep()
for face in faces:
rhino_face, rhino_surface = self._create_brep_face(face)
for loop in face.loops:
rhino_loop = self._brep.Loops.Add(Rhino.Geometry.BrepLoopType.Outer, rhino_face)
for edge in loop.edges:
start_vertex, end_vertex = self._add_edge_vertices(edge)
rhino_edge = self._add_edge(edge, start_vertex, end_vertex)
rhino_2d_curve = self._create_trim_curve(rhino_edge, rhino_surface)
self._add_trim(rhino_2d_curve, rhino_edge, rhino_loop)

self._brep.Repair(TOLERANCE)
self._brep.JoinNakedEdges(
TOLERANCE
) # without this, Brep.Trim() led to some weird results on de-serialized Breps
self._validate_brep()

def _validate_brep(self):
if self._brep.IsValid:
return

error_message = ""
valid_topo, log_topo = self._brep.IsValidTopology()
valid_tol, log_tol = self._brep.IsValidTolerancesAndFlags()
valid_geo, log_geo = self._brep.IsValidGeometry()
if not valid_geo:
error_message += "Invalid geometry:\n{}\n".format(log_geo)
if not valid_topo:
error_message += "Invalid topology:\n{}\n".format(log_topo)
if not valid_tol:
error_message += "Invalid tolerances:\n{}\n".format(log_tol)

raise BrepInvalidError(error_message)

def _create_brep_face(self, face):
# Geometry
surface_index = self._brep.AddSurface(face.native_surface)
brep_surface = self._brep.Surfaces.Item[surface_index]
# Topology
brep_face = self._brep.Faces.Add(surface_index)
return brep_face, brep_surface

def _add_edge_vertices(self, edge):
start_vertex = self._brep.Vertices.Add(point_to_rhino(edge.start_vertex.point), TOLERANCE)
end_vertex = self._brep.Vertices.Add(point_to_rhino(edge.end_vertex.point), TOLERANCE)
return start_vertex, end_vertex

def _add_edge(self, edge, start_vertex, end_vertex):
# Geometry
curve_index = self._brep.AddEdgeCurve(edge.curve)
# Topology
rhino_edge = self._brep.Edges.Add(start_vertex, end_vertex, curve_index, TOLERANCE)
return rhino_edge

def _add_trim(self, rhino_trim_curve, rhino_edge, rhino_loop):
# Geometry
trim_curve_index = self._brep.AddTrimCurve(rhino_trim_curve)
# Topology
trim = self._brep.Trims.Add(rhino_edge, True, rhino_loop, trim_curve_index)
trim.IsoStatus = getattr(
Rhino.Geometry.IsoStatus, "None"
) # IsoStatus.None makes lint, IDE and even Python angry
trim.TrimType = Rhino.Geometry.BrepTrimType.Boundary
trim.SetTolerances(TOLERANCE, TOLERANCE)

@staticmethod
def _create_trim_curve(rhino_edge, rhino_surface):
curve_2d = rhino_surface.Pullback(rhino_edge.EdgeCurve, TOLERANCE)
curve_2d.Reverse()
return curve_2d
Loading