Skip to content

Commit a8707ce

Browse files
Implement locationAt (#404)
* Initial implementation of locationAt * Added tests
1 parent 747631f commit a8707ce

File tree

2 files changed

+102
-5
lines changed

2 files changed

+102
-5
lines changed

cadquery/occ_impl/shapes.py

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
# collection of pints (used for spline construction)
3838
from OCP.TColgp import TColgp_HArray1OfPnt
39-
from OCP.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
39+
from OCP.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface, BRepAdaptor_HCurve
4040
from OCP.BRepBuilderAPI import (
4141
BRepBuilderAPI_MakeVertex,
4242
BRepBuilderAPI_MakeEdge,
@@ -168,6 +168,17 @@
168168
from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds
169169
from OCP.TopTools import TopTools_HSequenceOfShape
170170

171+
from OCP.GCPnts import GCPnts_AbscissaPoint
172+
173+
from OCP.GeomFill import (
174+
GeomFill_Frenet,
175+
GeomFill_CorrectedFrenet,
176+
GeomFill_CorrectedFrenet,
177+
GeomFill_DiscreteTrihedron,
178+
GeomFill_ConstantBiNormal,
179+
GeomFill_DraftTrihedron,
180+
GeomFill_TrihedronLaw,
181+
)
171182
from math import pi, sqrt
172183
import warnings
173184

@@ -1070,13 +1081,69 @@ def makeTangentArc(cls: Type["Edge"], v1: Vector, v2: Vector, v3: Vector) -> "Ed
10701081
@classmethod
10711082
def makeLine(cls: Type["Edge"], v1: Vector, v2: Vector) -> "Edge":
10721083
"""
1073-
Create a line between two points
1074-
:param v1: Vector that represents the first point
1075-
:param v2: Vector that represents the second point
1076-
:return: A linear edge between the two provided points
1084+
Create a line between two points
1085+
:param v1: Vector that represents the first point
1086+
:param v2: Vector that represents the second point
1087+
:return: A linear edge between the two provided points
10771088
"""
10781089
return cls(BRepBuilderAPI_MakeEdge(v1.toPnt(), v2.toPnt()).Edge())
10791090

1091+
def locationAt(
1092+
self,
1093+
d: float,
1094+
mode: Literal["length", "parameter"] = "length",
1095+
frame: Literal["frenet", "corrected"] = "frenet",
1096+
) -> Location:
1097+
"""Generate location along the curve
1098+
:param d: distance or parameter value
1099+
:param mode: position calculation mode (default: length)
1100+
:param frame: moving frame calculation method (default: frenet)
1101+
:return: A Location object representing local coordinate system at the specified distance.
1102+
"""
1103+
1104+
curve = BRepAdaptor_Curve(self.wrapped)
1105+
1106+
if mode == "length":
1107+
l = GCPnts_AbscissaPoint.Length_s(curve)
1108+
param = GCPnts_AbscissaPoint(curve, l * d, 0).Parameter()
1109+
else:
1110+
param = d
1111+
1112+
law: GeomFill_TrihedronLaw
1113+
if frame == "frenet":
1114+
law = GeomFill_Frenet()
1115+
else:
1116+
law = GeomFill_CorrectedFrenet()
1117+
1118+
law.SetCurve(BRepAdaptor_HCurve(curve))
1119+
1120+
tangent, normal, binormal = gp_Vec(), gp_Vec(), gp_Vec()
1121+
1122+
law.D0(param, tangent, normal, binormal)
1123+
pnt = curve.Value(param)
1124+
1125+
T = gp_Trsf()
1126+
T.SetTransformation(
1127+
gp_Ax3(pnt, gp_Dir(tangent.XYZ()), gp_Dir(normal.XYZ())), gp_Ax3()
1128+
)
1129+
1130+
return Location(TopLoc_Location(T))
1131+
1132+
def locations(
1133+
self,
1134+
ds: Iterable[float],
1135+
mode: Literal["length", "parameter"] = "length",
1136+
frame: Literal["frenet", "corrected"] = "frenet",
1137+
) -> List[Location]:
1138+
"""Generate location along the curve
1139+
:param ds: distance or parameter values
1140+
:param mode: position calculation mode (default: length)
1141+
:param frame: moving frame calculation method (default: frenet)
1142+
:return: A list of Location objects representing local coordinate systems at the specified distances.
1143+
"""
1144+
1145+
return [self.locationAt(d, mode, frame) for d in ds]
1146+
10801147

10811148
class Wire(Shape, Mixin1D):
10821149
"""

tests/test_cadquery.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3559,3 +3559,33 @@ def testConsolidateWires(self):
35593559

35603560
w1 = Workplane().consolidateWires()
35613561
self.assertEqual(w1.size(), 0)
3562+
3563+
def testLocationAt(self):
3564+
3565+
r = 1
3566+
e = Wire.makeHelix(r, r, r).Edges()[0]
3567+
3568+
locs_frenet = e.locations([0, 1], frame="frenet")
3569+
3570+
T1 = locs_frenet[0].wrapped.Transformation()
3571+
T2 = locs_frenet[1].wrapped.Transformation()
3572+
3573+
self.assertAlmostEqual(T1.TranslationPart().X(), r, 6)
3574+
self.assertAlmostEqual(T2.TranslationPart().X(), r, 6)
3575+
self.assertAlmostEqual(
3576+
T1.GetRotation().GetRotationAngle(), -T2.GetRotation().GetRotationAngle(), 6
3577+
)
3578+
3579+
ga = e._geomAdaptor()
3580+
3581+
locs_corrected = e.locations(
3582+
[ga.FirstParameter(), ga.LastParameter()],
3583+
mode="parameter",
3584+
frame="corrected",
3585+
)
3586+
3587+
T3 = locs_corrected[0].wrapped.Transformation()
3588+
T4 = locs_corrected[1].wrapped.Transformation()
3589+
3590+
self.assertAlmostEqual(T3.TranslationPart().X(), r, 6)
3591+
self.assertAlmostEqual(T4.TranslationPart().X(), r, 6)

0 commit comments

Comments
 (0)