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])
|
||||
|
||||
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
|
||||
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):
|
||||
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):
|
||||
from OCP.TColgp import TColgp_Array1OfVec
|
||||
@ -146,6 +146,7 @@ from OCP.BRepAlgoAPI import (
|
||||
)
|
||||
|
||||
from OCP.Geom import (
|
||||
Geom_BezierCurve,
|
||||
Geom_ConicalSurface,
|
||||
Geom_CylindricalSurface,
|
||||
Geom_Surface,
|
||||
@ -2091,6 +2092,26 @@ class Edge(Shape, Mixin1D):
|
||||
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):
|
||||
"""
|
||||
|
||||
@ -859,6 +859,23 @@ class Sketch(object):
|
||||
|
||||
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:
|
||||
"""
|
||||
Connect last edge to the first one.
|
||||
|
||||
@ -470,6 +470,19 @@ def test_edge_interface():
|
||||
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():
|
||||
|
||||
s1 = Sketch()
|
||||
|
||||
@ -250,3 +250,52 @@ class TestWorkplanes(BaseTest):
|
||||
(bbBox.xlen, bbBox.ylen, bbBox.zlen), (1.0, 1.0, 1.0), 4
|
||||
)
|
||||
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