Skip to content

Commit

Permalink
Implement locationAt (#404)
Browse files Browse the repository at this point in the history
* Initial implementation of locationAt

* Added tests
  • Loading branch information
adam-urbanczyk authored Jul 15, 2020
1 parent 747631f commit a8707ce
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 5 deletions.
77 changes: 72 additions & 5 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

# collection of pints (used for spline construction)
from OCP.TColgp import TColgp_HArray1OfPnt
from OCP.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
from OCP.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface, BRepAdaptor_HCurve
from OCP.BRepBuilderAPI import (
BRepBuilderAPI_MakeVertex,
BRepBuilderAPI_MakeEdge,
Expand Down Expand Up @@ -168,6 +168,17 @@
from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds
from OCP.TopTools import TopTools_HSequenceOfShape

from OCP.GCPnts import GCPnts_AbscissaPoint

from OCP.GeomFill import (
GeomFill_Frenet,
GeomFill_CorrectedFrenet,
GeomFill_CorrectedFrenet,
GeomFill_DiscreteTrihedron,
GeomFill_ConstantBiNormal,
GeomFill_DraftTrihedron,
GeomFill_TrihedronLaw,
)
from math import pi, sqrt
import warnings

Expand Down Expand Up @@ -1070,13 +1081,69 @@ def makeTangentArc(cls: Type["Edge"], v1: Vector, v2: Vector, v3: Vector) -> "Ed
@classmethod
def makeLine(cls: Type["Edge"], v1: Vector, v2: Vector) -> "Edge":
"""
Create a line between two points
:param v1: Vector that represents the first point
:param v2: Vector that represents the second point
:return: A linear edge between the two provided points
Create a line between two points
:param v1: Vector that represents the first point
:param v2: Vector that represents the second point
:return: A linear edge between the two provided points
"""
return cls(BRepBuilderAPI_MakeEdge(v1.toPnt(), v2.toPnt()).Edge())

def locationAt(
self,
d: float,
mode: Literal["length", "parameter"] = "length",
frame: Literal["frenet", "corrected"] = "frenet",
) -> Location:
"""Generate location along the curve
:param d: distance or parameter value
:param mode: position calculation mode (default: length)
:param frame: moving frame calculation method (default: frenet)
:return: A Location object representing local coordinate system at the specified distance.
"""

curve = BRepAdaptor_Curve(self.wrapped)

if mode == "length":
l = GCPnts_AbscissaPoint.Length_s(curve)
param = GCPnts_AbscissaPoint(curve, l * d, 0).Parameter()
else:
param = d

law: GeomFill_TrihedronLaw
if frame == "frenet":
law = GeomFill_Frenet()
else:
law = GeomFill_CorrectedFrenet()

law.SetCurve(BRepAdaptor_HCurve(curve))

tangent, normal, binormal = gp_Vec(), gp_Vec(), gp_Vec()

law.D0(param, tangent, normal, binormal)
pnt = curve.Value(param)

T = gp_Trsf()
T.SetTransformation(
gp_Ax3(pnt, gp_Dir(tangent.XYZ()), gp_Dir(normal.XYZ())), gp_Ax3()
)

return Location(TopLoc_Location(T))

def locations(
self,
ds: Iterable[float],
mode: Literal["length", "parameter"] = "length",
frame: Literal["frenet", "corrected"] = "frenet",
) -> List[Location]:
"""Generate location along the curve
:param ds: distance or parameter values
:param mode: position calculation mode (default: length)
:param frame: moving frame calculation method (default: frenet)
:return: A list of Location objects representing local coordinate systems at the specified distances.
"""

return [self.locationAt(d, mode, frame) for d in ds]


class Wire(Shape, Mixin1D):
"""
Expand Down
30 changes: 30 additions & 0 deletions tests/test_cadquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3559,3 +3559,33 @@ def testConsolidateWires(self):

w1 = Workplane().consolidateWires()
self.assertEqual(w1.size(), 0)

def testLocationAt(self):

r = 1
e = Wire.makeHelix(r, r, r).Edges()[0]

locs_frenet = e.locations([0, 1], frame="frenet")

T1 = locs_frenet[0].wrapped.Transformation()
T2 = locs_frenet[1].wrapped.Transformation()

self.assertAlmostEqual(T1.TranslationPart().X(), r, 6)
self.assertAlmostEqual(T2.TranslationPart().X(), r, 6)
self.assertAlmostEqual(
T1.GetRotation().GetRotationAngle(), -T2.GetRotation().GetRotationAngle(), 6
)

ga = e._geomAdaptor()

locs_corrected = e.locations(
[ga.FirstParameter(), ga.LastParameter()],
mode="parameter",
frame="corrected",
)

T3 = locs_corrected[0].wrapped.Transformation()
T4 = locs_corrected[1].wrapped.Transformation()

self.assertAlmostEqual(T3.TranslationPart().X(), r, 6)
self.assertAlmostEqual(T4.TranslationPart().X(), r, 6)

0 comments on commit a8707ce

Please sign in to comment.