Add Bézier curve support to Workplane and Sketch (#1529)

This commit is contained in:
Dov Grobgeld
2024-03-23 17:15:54 +02:00
committed by GitHub
parent 00fdd71d7d
commit 76fbff7158
5 changed files with 134 additions and 1 deletions

View File

@ -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:
"""

View File

@ -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):
"""

View File

@ -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.

View File

@ -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()

View File

@ -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)