Added Workplane.tangentArcToPoint, occ_impl.shapes.Edge.makeTangentArc
This commit is contained in:
@ -1160,6 +1160,28 @@ class Workplane(CQ):
|
||||
else:
|
||||
return p
|
||||
|
||||
def _findFromEdge(self, useLocalCoords=False):
|
||||
"""
|
||||
Finds the previous edge for an operation that needs it, similar to
|
||||
method _findFromPoint. Examples include tangentArcEndpoint.
|
||||
|
||||
:param useLocalCoords: selects whether the point is returned
|
||||
in local coordinates or global coordinates.
|
||||
:return: an Edge
|
||||
"""
|
||||
obj = self.objects[-1]
|
||||
|
||||
if not isinstance(obj, Edge):
|
||||
raise RuntimeError(
|
||||
"Previous Edge requested, but the previous object was of "
|
||||
+ f"type {type(obj)}, not an Edge."
|
||||
)
|
||||
|
||||
if useLocalCoords:
|
||||
obj = self.plane.toLocalCoords(obj)
|
||||
|
||||
return obj
|
||||
|
||||
def rarray(self, xSpacing, ySpacing, xCount, yCount, center=True):
|
||||
"""
|
||||
Creates an array of points and pushes them onto the stack.
|
||||
@ -1660,6 +1682,37 @@ class Workplane(CQ):
|
||||
else:
|
||||
return self.sagittaArc(endPoint, -sag, forConstruction)
|
||||
|
||||
def tangentArcEndpoint(self, endpoint, forConstruction=False, relative=True):
|
||||
"""
|
||||
Draw an arc as a tangent from the end of the current edge to endpoint.
|
||||
|
||||
:param endpoint: point for the arc to end at
|
||||
:type endpoint: 2-tuple or Vector
|
||||
:param relative: True if endpoint is specified relative to the current point, False if endpoint is in workplane coordinates
|
||||
:type relative: Bool
|
||||
:return: a Workplane object with an arc on the stack
|
||||
|
||||
Requires the the current first object on the stack is an Edge, as would
|
||||
be the case after a lineTo operation or similar.
|
||||
"""
|
||||
|
||||
if not isinstance(endpoint, Vector):
|
||||
endpoint = Vector(endpoint)
|
||||
if relative:
|
||||
endpoint = endpoint + self._findFromPoint(useLocalCoords=True)
|
||||
endpoint = self.plane.toWorldCoords(endpoint)
|
||||
|
||||
previousEdge = self._findFromEdge()
|
||||
|
||||
arc = Edge.makeTangentArc(
|
||||
previousEdge.endPoint(), previousEdge.tangentAt(1), endpoint
|
||||
)
|
||||
|
||||
if not forConstruction:
|
||||
self._addPendingEdge(arc)
|
||||
|
||||
return self.newObject([arc])
|
||||
|
||||
def rotateAndCopy(self, matrix):
|
||||
"""
|
||||
Makes a copy of all edges on the stack, rotates them according to the
|
||||
@ -2094,11 +2147,13 @@ class Workplane(CQ):
|
||||
|
||||
:return: a CQ object with a completed wire on the stack, if possible.
|
||||
|
||||
After 2-d drafting with lineTo,threePointArc, and polyline, it is necessary
|
||||
to convert the edges produced by these into one or more wires.
|
||||
After 2-d drafting with methods such as lineTo, threePointArc,
|
||||
tangentArcEndpoint and polyline, it is necessary to convert the edges
|
||||
produced by these into one or more wires.
|
||||
|
||||
When a set of edges is closed, cadQuery assumes it is safe to build the group of edges
|
||||
into a wire. This example builds a simple triangular prism::
|
||||
When a set of edges is closed, cadQuery assumes it is safe to build
|
||||
the group of edges into a wire. This example builds a simple triangular
|
||||
prism::
|
||||
|
||||
s = Workplane().lineTo(1,0).lineTo(1,1).close().extrude(0.2)
|
||||
"""
|
||||
|
@ -790,6 +790,21 @@ class Edge(Shape, Mixin1D):
|
||||
|
||||
return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge())
|
||||
|
||||
@classmethod
|
||||
def makeTangentArc(cls, v1, v2, v3):
|
||||
"""
|
||||
Makes a tangent arc from point v1, in the direction of v2 and ends at
|
||||
v3.
|
||||
:param cls:
|
||||
:param v1: start vector
|
||||
:param v2: tangent vector
|
||||
:param v3: end vector
|
||||
:return: an edge
|
||||
"""
|
||||
circle_geom = GC_MakeArcOfCircle(v1.toPnt(), v2._wrapped, v3.toPnt()).Value()
|
||||
|
||||
return cls(BRepBuilderAPI_MakeEdge(circle_geom).Edge())
|
||||
|
||||
@classmethod
|
||||
def makeLine(cls, v1, v2):
|
||||
"""
|
||||
|
@ -54,6 +54,7 @@ All 2-d operations require a **Workplane** object to be created.
|
||||
Workplane.threePointArc
|
||||
Workplane.sagittaArc
|
||||
Workplane.radiusArc
|
||||
Workplane.tangentArcEndpoint
|
||||
Workplane.rotateAndCopy
|
||||
Workplane.mirrorY
|
||||
Workplane.mirrorX
|
||||
|
@ -82,6 +82,24 @@ class TestCadObjects(BaseTest):
|
||||
(-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3
|
||||
)
|
||||
|
||||
def testEdgeWrapperMakeTangentArc(self):
|
||||
tangent_arc = Edge.makeTangentArc(
|
||||
Vector(1, 1), # starts at 1, 1
|
||||
Vector(0, 1), # tangent at start of arc is in the +y direction
|
||||
Vector(2, 1), # arc curves 180 degrees and ends at 2, 1
|
||||
)
|
||||
self.assertTupleAlmostEquals((1, 1, 0), tangent_arc.startPoint().toTuple(), 3)
|
||||
self.assertTupleAlmostEquals((2, 1, 0), tangent_arc.endPoint().toTuple(), 3)
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, 1, 0), tangent_arc.tangentAt(locationParam=0).toTuple(), 3
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(1, 0, 0), tangent_arc.tangentAt(locationParam=0.5).toTuple(), 3
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
(0, -1, 0), tangent_arc.tangentAt(locationParam=1).toTuple(), 3
|
||||
)
|
||||
|
||||
def testFaceWrapperMakePlane(self):
|
||||
mplane = Face.makePlane(10, 10)
|
||||
|
||||
|
@ -3053,3 +3053,88 @@ class TestCadQuery(BaseTest):
|
||||
plate_4 = Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
|
||||
self.assertTrue(plate_4.val().isValid())
|
||||
self.assertAlmostEqual(plate_4.val().Volume(), 7.760559490, 3)
|
||||
|
||||
def testTangentArcToPoint(self):
|
||||
|
||||
# create a simple shape with tangents of straight edges and see if it has the correct area
|
||||
s0 = (
|
||||
Workplane("XY")
|
||||
.hLine(1)
|
||||
.tangentArcEndpoint((1, 1), relative=False)
|
||||
.hLineTo(0)
|
||||
.tangentArcEndpoint((0, 0), relative=False)
|
||||
.close()
|
||||
.extrude(1)
|
||||
)
|
||||
area0 = s0.faces(">Z").val().Area()
|
||||
self.assertAlmostEqual(area0, (1 + math.pi * 0.5 ** 2), 4)
|
||||
|
||||
# test relative coords
|
||||
s1 = (
|
||||
Workplane("XY")
|
||||
.hLine(1)
|
||||
.tangentArcEndpoint((0, 1), relative=True)
|
||||
.hLineTo(0)
|
||||
.tangentArcEndpoint((0, -1), relative=True)
|
||||
.close()
|
||||
.extrude(1)
|
||||
)
|
||||
self.assertTupleAlmostEquals(
|
||||
s1.val().Center().toTuple(), s0.val().Center().toTuple(), 4
|
||||
)
|
||||
self.assertAlmostEqual(s1.val().Volume(), s0.val().Volume(), 4)
|
||||
|
||||
# consecutive tangent arcs
|
||||
s1 = (
|
||||
Workplane("XY")
|
||||
.vLine(2)
|
||||
.tangentArcEndpoint((1, 0))
|
||||
.tangentArcEndpoint((1, 0))
|
||||
.tangentArcEndpoint((1, 0))
|
||||
.vLine(-2)
|
||||
.close()
|
||||
.extrude(1)
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
s1.faces(">Z").val().Area(), 2 * 3 + 0.5 * math.pi * 0.5 ** 2, 4
|
||||
)
|
||||
|
||||
# tangentArc on the end of a spline
|
||||
# spline will be a simple arc of a circle, then finished off with a
|
||||
# tangentArcEndpoint
|
||||
angles = [idx * 1.5 * math.pi / 10 for idx in range(10)]
|
||||
pts = [(math.sin(a), math.cos(a)) for a in angles]
|
||||
s2 = (
|
||||
Workplane("XY")
|
||||
.spline(pts)
|
||||
.tangentArcEndpoint((0, 1), relative=False)
|
||||
.close()
|
||||
.extrude(1)
|
||||
)
|
||||
# volume should almost be pi, but not accurately because we need to
|
||||
# start with a spline
|
||||
self.assertAlmostEqual(s2.val().Volume(), math.pi, 1)
|
||||
# assert local coords are mapped to global correctly
|
||||
arc0 = (
|
||||
Workplane("XZ", origin=(1, 1, 1)).hLine(1).tangentArcEndpoint((1, 1)).val()
|
||||
)
|
||||
self.assertTupleAlmostEquals(arc0.endPoint().toTuple(), (3, 1, 2), 4)
|
||||
|
||||
def test_findFromEdge(self):
|
||||
part = Workplane("XY", origin=(1, 1, 1)).hLine(1)
|
||||
found_edge = part._findFromEdge(useLocalCoords=False)
|
||||
self.assertTupleAlmostEquals(found_edge.startPoint().toTuple(), (1, 1, 1), 3)
|
||||
self.assertTupleAlmostEquals(found_edge.Center().toTuple(), (1.5, 1, 1), 3)
|
||||
self.assertTupleAlmostEquals(found_edge.endPoint().toTuple(), (2, 1, 1), 3)
|
||||
found_edge = part._findFromEdge(useLocalCoords=True)
|
||||
self.assertTupleAlmostEquals(found_edge.endPoint().toTuple(), (1, 0, 0), 3)
|
||||
# check _findFromEdge can find a spline
|
||||
pts = [(0, 0), (0, 1), (1, 2), (2, 4)]
|
||||
spline0 = Workplane("XZ").spline(pts)._findFromEdge()
|
||||
self.assertTupleAlmostEquals((2, 0, 4), spline0.endPoint().toTuple(), 3)
|
||||
# check method fails if no edge is present
|
||||
part2 = Workplane("XY").box(1, 1, 1)
|
||||
with self.assertRaises(RuntimeError):
|
||||
part2._findFromEdge()
|
||||
with self.assertRaises(RuntimeError):
|
||||
part2._findFromEdge(useLocalCoords=True)
|
||||
|
Reference in New Issue
Block a user