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

* Added `compas_occ.geometry.NurbsCurve.segment`.
* Added `compas_occ.geometry.NurbsCurve.segmented`.
* Added `compas_occ.geometry.NurbsCurve.closest_point`.

### Changed

### Removed
Binary file added docs/_images/example_curve_closest_point.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_images/example_segmentation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions docs/examples/curve_closest_point.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from compas.geometry import Point, Polyline
from compas_occ.geometry import NurbsCurve

from compas_view2.app import App

points = [Point(0, 0, 0), Point(3, 0, 2), Point(6, 0, -3), Point(8, 0, 0)]
curve = NurbsCurve.from_interpolation(points)

projection_point = Point(2, -1, 0)

closest_point, t = curve.closest_point(projection_point, parameter=True)

print(curve.point_at(t) == closest_point)

# ==============================================================================
# Visualisation
# ==============================================================================

view = App()

view.add(Polyline(curve.locus()), linewidth=3)
view.add(projection_point, pointcolor = (0, 0, 1))
view.add(closest_point, pointcolor = (1, 0, 0))

view.run()
14 changes: 14 additions & 0 deletions docs/examples/curve_closest_point.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
********************************************************************************
Curve Closest Point
********************************************************************************

.. figure:: /_images/example_curve_closest_point.png
:figclass: figure
:class: figure-img img-fluid

.. literalinclude:: curve_closest_point.py
:language: python

.. code-block:: text

True
34 changes: 34 additions & 0 deletions docs/examples/curve_segmentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from compas.geometry import Point, Polyline
from compas_occ.geometry import NurbsCurve

from compas_view2.app import App


pointsA = [Point(0, 0, 0), Point(3, 6, 0), Point(6, -3, 3), Point(10, 0, 0)]
curveA = NurbsCurve.from_points(pointsA)

curveA.segment(u=0.2, v=0.5)

print(curveA.domain)


pointsB = [Point(0, -1, 0), Point(3, 5, 0), Point(6, -4, 3), Point(10, -1, 0)]
curveB = NurbsCurve.from_points(pointsB)

segment = curveB.segmented(u=0.2, v=0.5)

print(curveB.domain)
print(segment.domain)

# ==============================================================================
# Visualisation
# ==============================================================================

view = App()

view.add(Polyline(curveA.locus()), linewidth=4, linecolor=(1, 0, 0))

view.add(Polyline(curveB.locus()), linewidth=1, linecolor=(0, 0, 0))
view.add(Polyline(segment.locus()), linewidth=4, linecolor=(0, 1, 0))

view.run()
17 changes: 17 additions & 0 deletions docs/examples/curve_segmentation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
********************************************************************************
Curve Segmentation
********************************************************************************

.. figure:: /_images/example_segmentation.png
:figclass: figure
:class: figure-img img-fluid

.. literalinclude:: curve_segmentation.py
:language: python

.. code-block:: text

(0.2, 0.5)

(0.0, 1.0)
(0.2, 0.5)
68 changes: 65 additions & 3 deletions src/compas_occ/geometry/curves/nurbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from OCC.Core.gp import gp_Trsf
from OCC.Core.Geom import Geom_BSplineCurve
from OCC.Core.GeomAPI import GeomAPI_Interpolate
from OCC.Core.GeomAPI import GeomAPI_ProjectPointOnCurve
from OCC.Core.GeomAdaptor import GeomAdaptor_Curve
from OCC.Core.GCPnts import GCPnts_AbscissaPoint_Length
from OCC.Core.Bnd import Bnd_Box
Expand Down Expand Up @@ -601,9 +602,27 @@ def frame_at(self, t):
"""
pass

def closest_point(self, point, distance=None):
"""Compute the closest point on the curve to a given point."""
pass
def closest_point(self, point: Point, parameter: bool = False) -> Point:
"""Compute the closest point on the curve to a given point.

Parameters
----------
point : Point
The point to project orthogonally to the curve.
parameter : bool, optional
Return the projected point as well as the curve parameter.

Returns
-------
Point or tuple
The nearest point on the curve, if ``parameter`` is false.
The nearest as (point, parameter) tuple, if ``parameter`` is true.
"""
projector = GeomAPI_ProjectPointOnCurve(point.to_occ(), self.occ_curve)
point = Point.from_occ(projector.NearestPoint())
if not parameter:
return point
return point, projector.LowerDistanceParameter()

def divide_by_count(self, count):
"""Divide the curve into a specific number of equal length segments."""
Expand Down Expand Up @@ -631,3 +650,46 @@ def obb(self, precision: float = 0.0) -> Box:
def length(self, precision: float = 1e-3) -> float:
"""Compute the length of the curve."""
return GCPnts_AbscissaPoint_Length(GeomAdaptor_Curve(self.occ_curve))

def segment(self, u: float, v: float, precision: float = 1e-3) -> None:
"""Modifies this curve by segmenting it between the parameters u and v.
Copy link
Member

Choose a reason for hiding this comment

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

could you add a comment about what segmenting actually is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what you mean...
Maybe something like " a segment is a portion of the curve bounded by the points of two curve parameters u and v." ?

Copy link
Member

Choose a reason for hiding this comment

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

to something like "Reduces the curve to the segment between parameters u and v." is a lot more descriptive of what actually happens


Parameters
----------
u: float
v: float
tol: float, optional
default value is 1e-3

Returns
-------
None
Copy link
Member

Choose a reason for hiding this comment

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

return value should be updated to the type of the returned object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in the segment (vs. segmented) nothing is returned actually (using the same convention as in compas, e.g transform vs. transformed). -> in that case, should there then be no Return section at all? (in compas this appears inconsistent, sometimes it returns None, sometimes there is no return at all...)

Copy link
Member

Choose a reason for hiding this comment

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

returning nothing or returning None is the same. but you are right that the return type was indeed correct since the curve is modified in place


"""
if u > v:
u, v = v, u
s, e = self.domain
if u < s or v > e:
raise ValueError('At least one of the given parameters is outside the curve domain.')
Comment on lines +672 to +673
Copy link
Member

Choose a reason for hiding this comment

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

as far as i understand the OCC docs, is this requirement not necessary

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in the case of e.g. u<v, Occ gives this error:
"RuntimeError: Standard_ConstructionErrorGeom_BSplineCurve::InsertKnots raised from method Segment of class Geom_BSplineCurve"
it just doesn't really become clear in my opinion what the problem is in this descriptionion.
What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

yes just leave it as is...

if u == v:
raise ValueError('The given domain is zero length.')
self.occ_curve.Segment(u, v, precision)

def segmented(self, u: float, v: float, precision: float = 1e-3) -> 'NurbsCurve':
"""Returns a copy of this curve by segmenting it between the parameters u and v.

Parameters
----------
u: float
v: float
tol: float,optional
default value is 1e-3

Returns
-------
NurbsCurve

"""
copy = self.copy()
copy.occ_curve.Segment(u, v, precision)
return copy