33from compas .geometry import Frame
44from compas .geometry import Curve
55from compas .geometry import Box
6+ from compas .geometry import distance_point_point
7+
68
79from OCC .Core .gp import gp_Trsf
810from OCC .Core .gp import gp_Pnt
1214from OCC .Core .GCPnts import GCPnts_AbscissaPoint_Length
1315from OCC .Core .Bnd import Bnd_Box
1416from OCC .Core .BndLib import BndLib_Add3dCurve_Add
17+ from OCC .Core .GeomAPI import GeomAPI_ProjectPointOnCurve
18+ from OCC .Core .GeomAPI import GeomAPI_ExtremaCurveCurve
19+ from OCC .Core .TopoDS import topods_Edge
20+ from OCC .Core .BRepBuilderAPI import BRepBuilderAPI_MakeEdge
21+ from OCC .Core .Interface import Interface_Static_SetCVal
22+ from OCC .Core .IFSelect import IFSelect_RetDone
23+ from OCC .Core .STEPControl import STEPControl_Writer
24+ from OCC .Core .STEPControl import STEPControl_AsIs
1525
1626from compas_occ .conversions import compas_point_from_occ_point
1727from compas_occ .conversions import compas_vector_from_occ_vector
@@ -30,14 +40,8 @@ class OCCCurve(Curve):
3040
3141 Attributes
3242 ----------
33- continuity : int, read-only
34- The degree of continuity of the curve.
35- degree : int, read-only
36- The degree of the curve.
3743 dimension : int, read-only
3844 The dimension of the curve.
39- order : int, read-only
40- The order of the curve (= degree + 1).
4145 domain : tuple[float, float], read-only
4246 The domain of the parameter space of the curve.
4347 start : :class:`~compas.geometry.Point`, read-only
@@ -60,49 +64,32 @@ def __init__(self, name=None):
6064 super ().__init__ (name = name )
6165 self ._occ_curve = None
6266
63- @property
64- def occ_curve (self ):
65- return self ._occ_curve
66-
67- @occ_curve .setter
68- def occ_curve (self , curve ):
69- self ._occ_curve = curve
67+ def __eq__ (self , other ):
68+ return self .occ_curve .IsEqual (other .occ_curve )
7069
7170 # ==============================================================================
7271 # Data
7372 # ==============================================================================
7473
7574 # ==============================================================================
76- # Customization
75+ # OCC Properties
7776 # ==============================================================================
7877
79- def __eq__ (self , other ):
80- return self .occ_curve .IsEqual (other .occ_curve )
81-
82- # ==============================================================================
83- # Constructors
84- # ==============================================================================
85-
86- @classmethod
87- def from_occ (cls , occ_curve ):
88- """Construct a NURBS curve from an existing OCC BSplineCurve.
89-
90- Parameters
91- ----------
92- occ_curve : Geom_BSplineCurve
78+ @property
79+ def occ_curve (self ):
80+ return self ._occ_curve
9381
94- Returns
95- -------
96- :class:`OCCNurbsCurve`
82+ @ occ_curve . setter
83+ def occ_curve ( self , curve ):
84+ self . _occ_curve = curve
9785
98- """
99- curve = cls ()
100- curve .occ_curve = occ_curve
101- return curve
86+ @property
87+ def occ_shape (self ):
88+ return BRepBuilderAPI_MakeEdge (self .occ_curve ).Shape ()
10289
103- # ==============================================================================
104- # Conversions
105- # ==============================================================================
90+ @ property
91+ def occ_edge ( self ):
92+ return topods_Edge ( self . occ_shape )
10693
10794 # ==============================================================================
10895 # Properties
@@ -113,24 +100,10 @@ def dimension(self):
113100 if self .occ_curve :
114101 return 3
115102
116- @property
117- def continuity (self ):
118- if self .occ_curve :
119- return self .occ_curve .Continuity ()
120-
121- @property
122- def degree (self ):
123- if self .occ_curve :
124- return self .occ_curve .Degree ()
125-
126103 @property
127104 def domain (self ):
128- return self .occ_curve .FirstParameter (), self .occ_curve .LastParameter ()
129-
130- @property
131- def order (self ):
132105 if self .occ_curve :
133- return self .degree + 1
106+ return self .occ_curve . FirstParameter (), self . occ_curve . LastParameter ()
134107
135108 @property
136109 def start (self ):
@@ -154,10 +127,83 @@ def is_periodic(self):
154127 if self .occ_curve :
155128 return self .occ_curve .IsPeriodic ()
156129
130+ # ==============================================================================
131+ # Constructors
132+ # ==============================================================================
133+
134+ @classmethod
135+ def from_occ (cls , occ_curve ):
136+ """Construct a NURBS curve from an existing OCC BSplineCurve.
137+
138+ Parameters
139+ ----------
140+ occ_curve : Geom_Curve
141+
142+ Returns
143+ -------
144+ :class:`OCCCurve`
145+
146+ """
147+ curve = cls ()
148+ curve .occ_curve = occ_curve
149+ return curve
150+
151+ # @classmethod
152+ # def from_circle(cls, circle):
153+ # """Construct a general parametric curve from a circle.
154+
155+ # Parameters
156+ # ----------
157+ # circle : :class:`~compas.geometry.Circle`
158+ # A primitive circle.
159+
160+ # Returns
161+ # -------
162+ # :class:`OCCCurve`
163+
164+ # """
165+
166+ # ==============================================================================
167+ # Conversions
168+ # ==============================================================================
169+
170+ def to_step (self , filepath , schema = "AP203" ):
171+ """Write the curve geometry to a STP file.
172+
173+ Parameters
174+ ----------
175+ filepath : str
176+ schema : str, optional
177+
178+ Returns
179+ -------
180+ None
181+
182+ """
183+ step_writer = STEPControl_Writer ()
184+ Interface_Static_SetCVal ("write.step.schema" , schema )
185+ step_writer .Transfer (self .occ_edge , STEPControl_AsIs )
186+ status = step_writer .Write (filepath )
187+ if status != IFSelect_RetDone :
188+ raise AssertionError ("Operation failed." )
189+
157190 # ==============================================================================
158191 # Methods
159192 # ==============================================================================
160193
194+ def copy (self ):
195+ """Make an independent copy of the current curve.
196+
197+ Returns
198+ -------
199+ :class:`compas_occ.geometry.OCCCurve`
200+
201+ """
202+ cls = type (self )
203+ curve = cls ()
204+ curve .occ_curve = self .occ_curve .Copy ()
205+ return curve
206+
161207 def transform (self , T ):
162208 """Transform this curve.
163209
@@ -288,6 +334,10 @@ def frame_at(self, t):
288334 self .occ_curve .D2 (t , point , uvec , vvec )
289335 return Frame (Point .from_occ (point ), Vector .from_occ (uvec ), Vector .from_occ (vvec ))
290336
337+ # ==============================================================================
338+ # Methods continued
339+ # ==============================================================================
340+
291341 def aabb (self , precision = 0.0 ):
292342 """Compute the axis aligned bounding box of the curve.
293343
@@ -304,7 +354,8 @@ def aabb(self, precision=0.0):
304354 BndLib_Add3dCurve_Add (GeomAdaptor_Curve (self .occ_curve ), precision , box )
305355 return Box .from_diagonal ((
306356 Point .from_occ (box .CornerMin ()),
307- Point .from_occ (box .CornerMax ())))
357+ Point .from_occ (box .CornerMax ())
358+ ))
308359
309360 def length (self , precision = 1e-3 ):
310361 """Compute the length of the curve.
@@ -319,3 +370,91 @@ def length(self, precision=1e-3):
319370
320371 """
321372 return GCPnts_AbscissaPoint_Length (GeomAdaptor_Curve (self .occ_curve ))
373+
374+ def closest_point (self , point , return_parameter = False ):
375+ """Compute the closest point on the curve to a given point.
376+ If an orthogonal projection is not possible, the start or end point is returned, whichever is closer.
377+
378+ Parameters
379+ ----------
380+ point : :class:`~compas.geometry.Point`
381+ The point to project to the curve.
382+ return_parameter : bool, optional
383+ If True, return the curve parameter in addition to the closest point.
384+
385+ Returns
386+ -------
387+ :class:`~compas.geometry.Point` | tuple[:class:`~compas.geometry.Point`, float]
388+ If `return_parameter` is False, the nearest point on the curve.
389+ If `return_parameter` is True, the nearest point on the curve and the corresponding parameter.
390+
391+ """
392+ projector = GeomAPI_ProjectPointOnCurve (point .to_occ (), self .occ_curve )
393+ try :
394+ point = Point .from_occ (projector .NearestPoint ())
395+ if return_parameter :
396+ parameter = projector .LowerDistanceParameter ()
397+ except RuntimeError as e :
398+ if e .args [0 ].startswith ('StdFail_NotDoneGeomAPI_ProjectPointOnCurve::NearestPoint' ):
399+ start = self .start
400+ end = self .end
401+ if distance_point_point (point , start ) <= distance_point_point (point , end ):
402+ point = start
403+ if return_parameter :
404+ parameter = self .occ_curve .FirstParameter ()
405+ else :
406+ point = end
407+ if return_parameter :
408+ parameter = self .occ_curve .LastParameter ()
409+ else :
410+ raise
411+ if not return_parameter :
412+ return point
413+ return point , parameter
414+
415+ def closest_parameters_curve (self , curve , return_distance = False ):
416+ """Computes the curve parameters where the curve is the closest to another given curve.
417+
418+ Parameters
419+ ----------
420+ curve : :class:`~compas_occ.geometry.OCCNurbsCurve`
421+ The curve to find the closest distance to.
422+ return_distance : bool, optional
423+ If True, return the minimum distance between the two curves in addition to the curve parameters.
424+
425+ Returns
426+ -------
427+ tuple[float, float] | tuple[tuple[float, float], float]
428+ If `return_distance` is False, the lowest distance parameters on the two curves.
429+ If `return_distance` is True, the distance between the two curves in addition to the curve parameters.
430+
431+ """
432+ extrema = GeomAPI_ExtremaCurveCurve (self .occ_curve , curve .occ_curve )
433+ if not return_distance :
434+ return extrema .LowerDistanceParameters ()
435+ return extrema .LowerDistanceParameters (), extrema .LowerDistance ()
436+
437+ def closest_points_curve (self , curve , return_distance = False ):
438+ """Computes the points on curves where the curve is the closest to another given curve.
439+
440+ Parameters
441+ ----------
442+ curve : :class:`~compas_occ.geometry.OCCNurbsCurve`
443+ The curve to find the closest distance to.
444+ return_distance : bool, optional
445+ If True, return the minimum distance between the curves in addition to the closest points.
446+
447+ Returns
448+ -------
449+ tuple[:class:`~compas.geometry.Point`, :class:`~compas.geometry.Point`] | tuple[tuple[:class:`~compas.geometry.Point`, :class:`~compas.geometry.Point`], float]
450+ If `return_distance` is False, the closest points.
451+ If `return_distance` is True, the distance in addition to the closest points.
452+
453+ """
454+ a , b = gp_Pnt (), gp_Pnt ()
455+ extrema = GeomAPI_ExtremaCurveCurve (self .occ_curve , curve .occ_curve )
456+ extrema .NearestPoints (a , b )
457+ points = Point .from_occ (a ), Point .from_occ (b )
458+ if not return_distance :
459+ return points
460+ return points , extrema .LowerDistance ()
0 commit comments