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: else:
return p 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): def rarray(self, xSpacing, ySpacing, xCount, yCount, center=True):
""" """
Creates an array of points and pushes them onto the stack. Creates an array of points and pushes them onto the stack.
@ -1660,6 +1682,37 @@ class Workplane(CQ):
else: else:
return self.sagittaArc(endPoint, -sag, forConstruction) 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): def rotateAndCopy(self, matrix):
""" """
Makes a copy of all edges on the stack, rotates them according to the 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. :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 After 2-d drafting with methods such as lineTo, threePointArc,
to convert the edges produced by these into one or more wires. 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 When a set of edges is closed, cadQuery assumes it is safe to build
into a wire. This example builds a simple triangular prism:: 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) 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()) 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 @classmethod
def makeLine(cls, v1, v2): 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.threePointArc
Workplane.sagittaArc Workplane.sagittaArc
Workplane.radiusArc Workplane.radiusArc
Workplane.tangentArcEndpoint
Workplane.rotateAndCopy Workplane.rotateAndCopy
Workplane.mirrorY Workplane.mirrorY
Workplane.mirrorX Workplane.mirrorX

View File

@ -82,6 +82,24 @@ class TestCadObjects(BaseTest):
(-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3 (-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): def testFaceWrapperMakePlane(self):
mplane = Face.makePlane(10, 10) mplane = Face.makePlane(10, 10)

View File

@ -3053,3 +3053,88 @@ class TestCadQuery(BaseTest):
plate_4 = Workplane("XY").interpPlate(edge_wire, surface_points, thickness) plate_4 = Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
self.assertTrue(plate_4.val().isValid()) self.assertTrue(plate_4.val().isValid())
self.assertAlmostEqual(plate_4.val().Volume(), 7.760559490, 3) 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)