diff --git a/CHANGELOG.md b/CHANGELOG.md index 104f38037..605982f8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/_images/example_curve_closest_point.png b/docs/_images/example_curve_closest_point.png new file mode 100644 index 000000000..f223eeab0 Binary files /dev/null and b/docs/_images/example_curve_closest_point.png differ diff --git a/docs/_images/example_segmentation.png b/docs/_images/example_segmentation.png new file mode 100644 index 000000000..a7b2cb4d1 Binary files /dev/null and b/docs/_images/example_segmentation.png differ diff --git a/docs/examples/curve_closest_point.py b/docs/examples/curve_closest_point.py new file mode 100644 index 000000000..f8f89582a --- /dev/null +++ b/docs/examples/curve_closest_point.py @@ -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() diff --git a/docs/examples/curve_closest_point.rst b/docs/examples/curve_closest_point.rst new file mode 100644 index 000000000..4338ccc48 --- /dev/null +++ b/docs/examples/curve_closest_point.rst @@ -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 diff --git a/docs/examples/curve_segmentation.py b/docs/examples/curve_segmentation.py new file mode 100644 index 000000000..9a17ef849 --- /dev/null +++ b/docs/examples/curve_segmentation.py @@ -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() diff --git a/docs/examples/curve_segmentation.rst b/docs/examples/curve_segmentation.rst new file mode 100644 index 000000000..e76ff511d --- /dev/null +++ b/docs/examples/curve_segmentation.rst @@ -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) diff --git a/src/compas_occ/geometry/curves/nurbs.py b/src/compas_occ/geometry/curves/nurbs.py index d71ba2052..96c84c436 100644 --- a/src/compas_occ/geometry/curves/nurbs.py +++ b/src/compas_occ/geometry/curves/nurbs.py @@ -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 @@ -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.""" @@ -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. + + Parameters + ---------- + u: float + v: float + tol: float, optional + default value is 1e-3 + + Returns + ------- + None + + """ + 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.') + 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