Add Bézier curve support to Workplane and Sketch (#1529)
This commit is contained in:
@ -1629,6 +1629,39 @@ class Workplane(object):
|
|||||||
|
|
||||||
return self.newObject([p])
|
return self.newObject([p])
|
||||||
|
|
||||||
|
def bezier(
|
||||||
|
self: T,
|
||||||
|
listOfXYTuple: Iterable[VectorLike],
|
||||||
|
forConstruction: bool = False,
|
||||||
|
includeCurrent: bool = False,
|
||||||
|
makeWire: bool = False,
|
||||||
|
) -> T:
|
||||||
|
"""
|
||||||
|
Make a cubic Bézier curve by the provided points (2D or 3D).
|
||||||
|
|
||||||
|
:param listOfXYTuple: Bezier control points and end point.
|
||||||
|
All points except the last point are Bezier control points,
|
||||||
|
and the last point is the end point
|
||||||
|
:param includeCurrent: Use the current point as a starting point of the curve
|
||||||
|
:param makeWire: convert the resulting bezier edge to a wire
|
||||||
|
:return: a Workplane object with the current point at the end of the bezier
|
||||||
|
|
||||||
|
The Bézier Will begin at either current point or the first point
|
||||||
|
of listOfXYTuple, and end with the last point of listOfXYTuple
|
||||||
|
"""
|
||||||
|
allPoints = self._toVectors(listOfXYTuple, includeCurrent)
|
||||||
|
|
||||||
|
e = Edge.makeBezier(allPoints)
|
||||||
|
|
||||||
|
if makeWire:
|
||||||
|
rv_w = Wire.assembleEdges([e])
|
||||||
|
if not forConstruction:
|
||||||
|
self._addPendingWire(rv_w)
|
||||||
|
elif not forConstruction:
|
||||||
|
self._addPendingEdge(e)
|
||||||
|
|
||||||
|
return self.newObject([rv_w if makeWire else e])
|
||||||
|
|
||||||
# line a specified incremental amount from current point
|
# line a specified incremental amount from current point
|
||||||
def line(self: T, xDist: float, yDist: float, forConstruction: bool = False) -> T:
|
def line(self: T, xDist: float, yDist: float, forConstruction: bool = False) -> T:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -56,7 +56,7 @@ from OCP.gp import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Array of points (used for B-spline construction):
|
# Array of points (used for B-spline construction):
|
||||||
from OCP.TColgp import TColgp_HArray1OfPnt, TColgp_HArray2OfPnt
|
from OCP.TColgp import TColgp_HArray1OfPnt, TColgp_HArray2OfPnt, TColgp_Array1OfPnt
|
||||||
|
|
||||||
# Array of vectors (used for B-spline interpolation):
|
# Array of vectors (used for B-spline interpolation):
|
||||||
from OCP.TColgp import TColgp_Array1OfVec
|
from OCP.TColgp import TColgp_Array1OfVec
|
||||||
@ -146,6 +146,7 @@ from OCP.BRepAlgoAPI import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from OCP.Geom import (
|
from OCP.Geom import (
|
||||||
|
Geom_BezierCurve,
|
||||||
Geom_ConicalSurface,
|
Geom_ConicalSurface,
|
||||||
Geom_CylindricalSurface,
|
Geom_CylindricalSurface,
|
||||||
Geom_Surface,
|
Geom_Surface,
|
||||||
@ -2091,6 +2092,26 @@ class Edge(Shape, Mixin1D):
|
|||||||
BRepBuilderAPI_MakeEdge(Vector(v1).toPnt(), Vector(v2).toPnt()).Edge()
|
BRepBuilderAPI_MakeEdge(Vector(v1).toPnt(), Vector(v2).toPnt()).Edge()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def makeBezier(cls, points: List[Vector]) -> "Edge":
|
||||||
|
"""
|
||||||
|
Create a cubic Bézier Curve from the points.
|
||||||
|
|
||||||
|
:param points: a list of Vectors that represent the points.
|
||||||
|
The edge will pass through the first and the last point,
|
||||||
|
and the inner points are Bézier control points.
|
||||||
|
:return: An edge
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Convert to a TColgp_Array1OfPnt
|
||||||
|
arr = TColgp_Array1OfPnt(1, len(points))
|
||||||
|
for i, v in enumerate(points):
|
||||||
|
arr.SetValue(i + 1, Vector(v).toPnt())
|
||||||
|
|
||||||
|
bez = Geom_BezierCurve(arr)
|
||||||
|
|
||||||
|
return cls(BRepBuilderAPI_MakeEdge(bez).Edge())
|
||||||
|
|
||||||
|
|
||||||
class Wire(Shape, Mixin1D):
|
class Wire(Shape, Mixin1D):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -859,6 +859,23 @@ class Sketch(object):
|
|||||||
|
|
||||||
return self.spline(pts, None, False, tag, forConstruction)
|
return self.spline(pts, None, False, tag, forConstruction)
|
||||||
|
|
||||||
|
def bezier(
|
||||||
|
self: T,
|
||||||
|
pts: Iterable[Point],
|
||||||
|
tag: Optional[str] = None,
|
||||||
|
forConstruction: bool = False,
|
||||||
|
) -> T:
|
||||||
|
"""
|
||||||
|
Construct an bezier curve.
|
||||||
|
|
||||||
|
The edge will pass through the last points, and the inner points
|
||||||
|
are bezier control points.
|
||||||
|
"""
|
||||||
|
p1 = self._endPoint()
|
||||||
|
val = Edge.makeBezier([Vector(*p) for p in pts])
|
||||||
|
|
||||||
|
return self.edge(val, tag, forConstruction)
|
||||||
|
|
||||||
def close(self: T, tag: Optional[str] = None) -> T:
|
def close(self: T, tag: Optional[str] = None) -> T:
|
||||||
"""
|
"""
|
||||||
Connect last edge to the first one.
|
Connect last edge to the first one.
|
||||||
|
|||||||
@ -470,6 +470,19 @@ def test_edge_interface():
|
|||||||
assert len(s6.vertices()._selection) == 1
|
assert len(s6.vertices()._selection) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_bezier():
|
||||||
|
s1 = (
|
||||||
|
Sketch()
|
||||||
|
.segment((0, 0), (0, 0.5))
|
||||||
|
.bezier(((0, 0.5), (-1, 2), (1, 0.5), (5, 0)))
|
||||||
|
.bezier(((5, 0), (1, -0.5), (-1, -2), (0, -0.5)))
|
||||||
|
.close()
|
||||||
|
.assemble()
|
||||||
|
)
|
||||||
|
assert s1._faces.Area() == approx(5.35)
|
||||||
|
# What other kind of tests can we do?
|
||||||
|
|
||||||
|
|
||||||
def test_assemble():
|
def test_assemble():
|
||||||
|
|
||||||
s1 = Sketch()
|
s1 = Sketch()
|
||||||
|
|||||||
@ -250,3 +250,52 @@ class TestWorkplanes(BaseTest):
|
|||||||
(bbBox.xlen, bbBox.ylen, bbBox.zlen), (1.0, 1.0, 1.0), 4
|
(bbBox.xlen, bbBox.ylen, bbBox.zlen), (1.0, 1.0, 1.0), 4
|
||||||
)
|
)
|
||||||
self.assertAlmostEqual(r.findSolid().Volume(), 1.0, 5)
|
self.assertAlmostEqual(r.findSolid().Volume(), 1.0, 5)
|
||||||
|
|
||||||
|
def test_bezier_curve(self):
|
||||||
|
# Quadratic bezier
|
||||||
|
r = (
|
||||||
|
Workplane("XZ")
|
||||||
|
.bezier([(0, 0), (1, 2), (5, 0)])
|
||||||
|
.bezier([(1, -2), (0, 0)], includeCurrent=True)
|
||||||
|
.close()
|
||||||
|
.extrude(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
bbBox = r.findSolid().BoundingBox()
|
||||||
|
# Why is the bounding box larger than expected?
|
||||||
|
self.assertTupleAlmostEquals((bbBox.xlen, bbBox.ylen, bbBox.zlen), (5, 1, 2), 1)
|
||||||
|
self.assertAlmostEqual(r.findSolid().Volume(), 6.6666667, 4)
|
||||||
|
|
||||||
|
r = Workplane("XY").bezier([(0, 0), (1, 2), (2, -1), (5, 0)])
|
||||||
|
self.assertTrue(len(r.ctx.pendingEdges) == 1)
|
||||||
|
r = (
|
||||||
|
r.lineTo(5, -0.1)
|
||||||
|
.bezier([(2, -3), (1, 0), (0, 0)], includeCurrent=True)
|
||||||
|
.close()
|
||||||
|
.extrude(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
bbBox = r.findSolid().BoundingBox()
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(bbBox.xlen, bbBox.ylen, bbBox.zlen), (5, 2.06767, 1), 1
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(r.findSolid().Volume(), 4.975, 4)
|
||||||
|
|
||||||
|
# Test makewire by translate and loft example like in
|
||||||
|
# the documentation
|
||||||
|
r = Workplane("XY").bezier([(0, 0), (1, 2), (1, -1), (0, 0)], makeWire=True)
|
||||||
|
|
||||||
|
self.assertTrue(len(r.ctx.pendingWires) == 1)
|
||||||
|
r = r.translate((0, 0, 0.2)).toPending().loft()
|
||||||
|
self.assertAlmostEqual(r.findSolid().Volume(), 0.09, 4)
|
||||||
|
|
||||||
|
# Finally test forConstruction
|
||||||
|
r = Workplane("XY").bezier(
|
||||||
|
[(0, 0), (1, 2), (1, -1), (0, 0)], makeWire=True, forConstruction=True
|
||||||
|
)
|
||||||
|
self.assertTrue(len(r.ctx.pendingWires) == 0)
|
||||||
|
|
||||||
|
r = Workplane("XY").bezier(
|
||||||
|
[(0, 0), (1, 2), (2, -1), (5, 0)], forConstruction=True
|
||||||
|
)
|
||||||
|
self.assertTrue(len(r.ctx.pendingEdges) == 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user