Skip to content
Open
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
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@
- Panayiotis Papacharalambous <<papacharalambous@arch.ethz.ch>> [@papachap](https://github.com/papachap)
- Oliver Bucklin <<obucklin@arch.ethz.ch>> [@obucklin](https://github.com/obucklin)
- Dominik Reisach <<reisach@arch.ethz.ch>> [@dominikreisach](https://github.com/dominikreisach)
- Eric Gozzi <<eric.gozzi@arch.ethz.ch>> [@ericgozzi](https://github.com/ericgozzi)
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Added support for `.stp` file extension in addition to `.step` for `RhinoBrep.from_step()` and `RhinoBrep.to_step()` methods.
* Added `volume()` method to `compas.datastructures.Mesh` for computing the volume of closed meshes using signed volume of triangles.
* Added functions `warning`, `message`, `error` and `remark` to `compas_ghpython`.
* Added method `RhinoBrep.closest_point()`.
* Added attributes `RhinoBrepEdge.domain` and `RhinoBrepEdge.index` and methods `RhinoBrepEdge.closest_point()` and `RhinoBrepEdge.point_at`.
* Added method `RhinoBrepFace.point_at()`, `RhinoBrepFace.closest_point()`, `RhinoBrepFace.is_point_on_face()` and `RhinoBrepFace.is_point_on_boundary()`.
* Added method `RhinoBrepLoop.to_curve()`.
* Added attribute `RhinoBrepTrim.edge`.
* Added attribute `RhinoBrepVertex.index`.
* Added method `RhinoCurve.to_polyline()`.

### Changed

Expand Down
1 change: 0 additions & 1 deletion src/compas/geometry/shapes/torus.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,3 @@ def scale(self, factor):
"""
self.radius_axis *= factor
self.radius_pipe *= factor

19 changes: 19 additions & 0 deletions src/compas_rhino/geometry/brep/brep.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from compas_rhino.conversions import mesh_to_compas
from compas_rhino.conversions import mesh_to_rhino
from compas_rhino.conversions import plane_to_rhino
from compas_rhino.conversions import point_to_compas
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
Expand Down Expand Up @@ -1106,3 +1107,21 @@ def cap_planar_holes(self, tolerance=None):
self._brep = result
else:
raise BrepError("Failed to cap planar holes")

def closest_point(self, point):
"""
Returns the closest point on the Brep to the given point.

Parameters
----------
point : :class:`compas.geometry.Point`
The point to find the closest point on the Brep to.

Returns
-------
:class:`compas.geometry.Point`
The closest point on the Brep to the given point.

"""
rgpoint = self._brep.ClosestPoint(point_to_rhino(point))
return point_to_compas(rgpoint)
52 changes: 52 additions & 0 deletions src/compas_rhino/geometry/brep/edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class RhinoBrepEdge(BrepEdge):
True if the geometry of this edge is a line, False otherwise.
native_edge : :class:`Rhino.Geometry.BrepEdge`
The underlying BrepEdge object.
domain : tuple
The domain of the edge.
index : int
The index of the edge.

"""

Expand Down Expand Up @@ -153,6 +157,17 @@ def is_ellipse(self):
def length(self):
return self._mass_props.Length

@property
def domain(self):
rhino_domain = self._edge.Domain
min = rhino_domain.Min
max = rhino_domain.Max
return (min, max)

@property
def index(self):
return self._edge.EdgeIndex

# ==============================================================================
# Methods
# ==============================================================================
Expand Down Expand Up @@ -200,3 +215,40 @@ def _create_curve__from_data__(curve_type, curve_data, frame_data, domain):
raise ValueError("Unknown curve type: {}".format(curve_type))
curve.Domain = Rhino.Geometry.Interval(*domain)
return curve

def closest_point(self, point):
"""
Returns the parameter of the closest point on the edge to the given point.

Parameters
----------
point : :class:`compas.geometry.Point`
The point to project onto the edge.

Returns
-------
float
The parameter of the closest point on the edge.
"""
rgpoint = Rhino.Geometry.Point3d(point.x, point.y, point.z)
success, parameter = self._edge.ClosestPoint(rgpoint)
if not success:
raise ValueError("Failed to find closest point on edge")
return parameter

def point_at(self, parameter):
"""
Returns the point on the edge at the given parameter.

Parameters
----------
parameter : float
The parameter of the point on the edge.

Returns
-------
:class:`compas.geometry.Point`
The point on the edge at the given parameter.
"""
rgpoint = self._edge.PointAt(parameter)
return point_to_compas(rgpoint)
84 changes: 84 additions & 0 deletions src/compas_rhino/geometry/brep/face.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from compas_rhino.conversions import cylinder_to_rhino
from compas_rhino.conversions import frame_to_rhino_plane
from compas_rhino.conversions import plane_to_compas_frame
from compas_rhino.conversions import point_to_compas
from compas_rhino.conversions import sphere_to_compas
from compas_rhino.conversions import sphere_to_rhino
from compas_rhino.geometry import RhinoNurbsSurface
Expand Down Expand Up @@ -310,3 +311,86 @@ def frame_at(self, u, v):
if not success:
raise ValueError("Failed to get frame at uv parameters: ({},{}).".format(u, v))
return plane_to_compas_frame(rhino_plane)

def point_at(self, u, v):
"""Returns the point at the given uv parameters.

Parameters
----------
u : float
The u parameter.
v : float
The v parameter.

Returns
-------
:class:`compas.geometry.Point`
The point at the given uv parameters.
"""
rgpoint = self._face.PointAt(u, v)
return point_to_compas(rgpoint)

def closest_point(self, point):
"""Returns the closest point on the face to a give point.

Parameters
----------
point : :class:`compas.geometry.Point`
The point to find the closest point on the face to.

Returns
-------
tuple[float, float]
The u and v parameters of the closest point on the face.

"""
rgpoint = Rhino.Geometry.Point3d(point.x, point.y, point.z)
success, u, v = self._face.ClosestPoint(rgpoint)
if success:
return (u, v)
else:
raise ValueError("Failed to find closest point on face.")

def is_point_on_face(self, u, v):
"""Returns True if the point at the given uv parameters is on the face (inside or on the boundary).

Parameters
----------
u : float
The u parameter.
v : float
The v parameter.

Returns
-------
bool
True if the point is on the face (inside or on the boundary), False otherwise.
"""
relation = self._face.IsPointOnFace(u, v)
if relation == Rhino.Geometry.PointFaceRelation.Interior:
return True
if relation == Rhino.Geometry.PointFaceRelation.Boundary:
return True
if relation == Rhino.Geometry.PointFaceRelation.Exterior:
return False

def is_point_on_boundary(self, u, v):
"""Returns True if the point is on the boundary of the face.

Parameters
----------
u : float
The u parameter.
v : float
The v parameter.

Returns
-------
bool
True if the point is on the boundary of the face, False otherwise.
"""
relation = self._face.IsPointOnFace(u, v)
if relation == Rhino.Geometry.PointFaceRelation.Boundary:
return True
else:
return False
6 changes: 6 additions & 0 deletions src/compas_rhino/geometry/brep/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import Rhino # type: ignore

from compas.geometry import BrepLoop
from compas.geometry import Curve

from .edge import RhinoBrepEdge
from .trim import RhinoBrepTrim
Expand Down Expand Up @@ -127,3 +128,8 @@ def native_loop(self, rhino_loop):
self._loop = rhino_loop
self._type = int(self._loop.LoopType)
self._trims = [RhinoBrepTrim(trim) for trim in self._loop.Trims]

def to_curve(self):
curve = self._loop.To3dCurve()
curve = Curve.from_native(curve)
return curve
8 changes: 7 additions & 1 deletion src/compas_rhino/geometry/brep/trim.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from compas.geometry import BrepTrim
from compas_rhino.geometry import RhinoNurbsCurve
from compas_rhino.geometry.brep.edge import RhinoBrepEdge

from .vertex import RhinoBrepVertex

Expand All @@ -29,7 +30,8 @@ class RhinoBrepTrim(BrepTrim):
The end vertex of this trim.
vertices : list[:class:`compas_rhino.geometry.RhinoBrepVertex`], read-only
The list of vertices which comprise this trim (start and end).

edge : :class:compas_rhino.geometry.RhinoBrepEdge
The edge associated with this trim.
"""

def __init__(self, rhino_trim=None):
Expand Down Expand Up @@ -118,6 +120,10 @@ def iso_status(self):
def native_trim(self):
return self._trim

@property
def edge(self):
return RhinoBrepEdge(self._trim.Edge)

@native_trim.setter
def native_trim(self, rhino_trim):
self._trim = rhino_trim
Expand Down
6 changes: 6 additions & 0 deletions src/compas_rhino/geometry/brep/vertex.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class RhinoBrepVertex(BrepVertex):
The underlying Rhino BrepBertex object.
point : :class:`compas.geometry.Point`, read-only
The geometry of this vertex as a point in 3D space.
index : int
The index of the vertex.

"""

Expand Down Expand Up @@ -66,6 +68,10 @@ def __from_data__(cls, data, builder):
def point(self):
return self._point

@property
def index(self):
return self._vertex.VertexIndex

@property
def native_vertex(self):
return self._vertex
Expand Down
29 changes: 29 additions & 0 deletions src/compas_rhino/geometry/curves/curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
from compas_rhino.conversions import plane_to_rhino
from compas_rhino.conversions import point_to_compas
from compas_rhino.conversions import point_to_rhino
from compas_rhino.conversions import polyline_to_compas
from compas_rhino.conversions import transformation_to_rhino
from compas_rhino.conversions import vector_to_compas
from compas_rhino.conversions.exceptions import ConversionError


class RhinoCurve(Curve):
Expand Down Expand Up @@ -147,6 +149,33 @@ def from_rhino(cls, native_curve):
# Conversions
# ==============================================================================

def to_polyline(self, tolerance=1, angle_tolerance=1, minimum_lenght=0, maximum_length=1):
"""
Convert the curve to a polyline.

Parameters
----------
tolerance : float, optional
The tolerance. This is the maximum deviation from line midpoints to the curve.
angle_tolerance : float, optional
The angle tolerance in radians. This is the maximum deviation of the line directions.
minimum_lenght : float, optional
The minimum segment length.
maximum_length : float, optional
The maximum segment length.

Returns
-------
:class:`compas.geometry.Polyline`
The polyline representation of the curve.
"""
curve_polyline = self.native_curve.ToPolyline(tolerance, angle_tolerance, minimum_lenght, maximum_length)
polyline_created, polyline = curve_polyline.TryGetPolyline()
if polyline_created:
return polyline_to_compas(polyline)
else:
raise ConversionError("The curve cannot be converted to a polyline.")

# ==============================================================================
# Methods
# ==============================================================================
Expand Down
13 changes: 6 additions & 7 deletions tests/compas/geometry/test_capsule.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ def test_capsule_discretization(capsule):
def test_capsule_scaled():
"""Test that Capsule.scaled() returns a scaled copy without modifying the original."""
capsule = Capsule(radius=5.0, height=10.0)

# Test uniform scaling
scaled_capsule = capsule.scaled(0.5)

# Original should be unchanged
assert capsule.radius == 5.0
assert capsule.height == 10.0

# Scaled copy should have scaled dimensions
assert scaled_capsule.radius == 2.5
assert scaled_capsule.height == 5.0

# Test scaling with factor > 1
scaled_capsule_2 = capsule.scaled(2.0)
assert scaled_capsule_2.radius == 10.0
Expand All @@ -42,11 +42,10 @@ def test_capsule_scaled():
def test_capsule_scale():
"""Test that Capsule.scale() modifies the capsule in place."""
capsule = Capsule(radius=5.0, height=10.0)

# Test uniform scaling
capsule.scale(0.5)

# Capsule should be modified
assert capsule.radius == 2.5
assert capsule.height == 5.0

Loading