Added Workplane.tangentArcToPoint, occ_impl.shapes.Edge.makeTangentArc

This commit is contained in:
Marcus Boyd
2020-02-22 20:00:45 +10:30
parent cd66589e4e
commit b89bc44f86
5 changed files with 178 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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

View File

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