Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

popPendingEdges and Wires #678

Merged
merged 3 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 43 additions & 35 deletions cadquery/cq.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,30 @@ def __init__(self):
self.tolerance = 0.0001 # user specified tolerance
self.tags = {}

def popPendingEdges(self, errorOnEmpty: bool = True) -> List[Edge]:
"""
Get and clear pending edges.

:raises ValueError: if errorOnEmpty is True and no edges are present.
"""
if errorOnEmpty and not self.pendingEdges:
raise ValueError("No pending edges present")
out = self.pendingEdges
self.pendingEdges = []
return out

def popPendingWires(self, errorOnEmpty: bool = True) -> List[Wire]:
"""
Get and clear pending wires.

:raises ValueError: if errorOnEmpty is True and no wires are present.
"""
if errorOnEmpty and not self.pendingWires:
raise ValueError("No pending wires present")
out = self.pendingWires
self.pendingWires = []
return out


class Workplane(object):
"""
Expand Down Expand Up @@ -2098,6 +2122,8 @@ def _addPendingWire(self, wire: Wire) -> None:

def _consolidateWires(self) -> List[Wire]:

# note: do not use CQContext.popPendingEdges or Wires here, this method does not
# clear pending edges or wires.
wires = cast(
List[Union[Edge, Wire]],
[el for el in chain(self.ctx.pendingEdges, self.ctx.pendingWires)],
Expand Down Expand Up @@ -2135,39 +2161,33 @@ def wire(self, forConstruction: bool = False) -> "Workplane":
Returns a CQ object with all pending edges connected into a wire.

All edges on the stack that can be combined will be combined into a single wire object,
and other objects will remain on the stack unmodified
and other objects will remain on the stack unmodified. If there are no pending edges,
this method will just return self.

:param forConstruction: whether the wire should be used to make a solid, or if it is just
for reference
:type forConstruction: boolean. true if the object is only for reference

This method is primarily of use to plugin developers making utilities for 2-d construction.
This method should be called when a user operation implies that 2-d construction is
finished, and we are ready to begin working in 3d
finished, and we are ready to begin working in 3d.

SEE '2-d construction concepts' for a more detailed explanation of how CadQuery handles
edges, wires, etc
edges, wires, etc.

Any non edges will still remain.
"""

edges = self.ctx.pendingEdges

# do not consolidate if there are no free edges
if len(edges) == 0:
if len(self.ctx.pendingEdges) == 0:
return self

self.ctx.pendingEdges = []

others = []
for e in self.objects:
if type(e) != Edge:
others.append(e)

edges = self.ctx.popPendingEdges()
w = Wire.assembleEdges(edges)
if not forConstruction:
self._addPendingWire(w)

others = [e for e in self.objects if not isinstance(e, Edge)]

return self.newObject(others + [w])

def each(
Expand Down Expand Up @@ -2733,10 +2753,7 @@ def twistExtrude(
"""
# group wires together into faces based on which ones are inside the others
# result is a list of lists
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires))

# now all of the wires have been used to create an extrusion
self.ctx.pendingWires = []
wireSets = sortWiresByBuildOrder(self.ctx.popPendingWires())

# compute extrusion vector and extrude
eDir = self.plane.zDir.multiply(distance)
Expand Down Expand Up @@ -3186,13 +3203,12 @@ def cutThruAll(self, clean: bool = True, taper: float = 0) -> "Workplane":

:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
:raises ValueError: if there is no solid to subtract from in the chain
:raises ValueError: if there are no pending wires to cut with
:return: a CQ object with the resulting object selected

see :py:meth:`cutBlind` to cut material to a limited depth
"""
wires = self.ctx.pendingWires
self.ctx.pendingWires = []

wires = self.ctx.popPendingWires()
solidRef = self.findSolid()

rv = []
Expand All @@ -3213,8 +3229,7 @@ def loft(
Make a lofted solid, through the set of wires.
:return: a CQ object containing the created loft
"""
wiresToLoft = self.ctx.pendingWires
self.ctx.pendingWires = []
wiresToLoft = self.ctx.popPendingWires()

r: Shape = Solid.makeLoft(wiresToLoft, ruled)

Expand Down Expand Up @@ -3244,9 +3259,7 @@ def _extrude(
# group wires together into faces based on which ones are inside the others
# result is a list of lists

wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires))
# now all of the wires have been used to create an extrusion
self.ctx.pendingWires = []
wireSets = sortWiresByBuildOrder(self.ctx.popPendingWires())

# compute extrusion vector and extrude
eDir = self.plane.zDir.multiply(distance)
Expand Down Expand Up @@ -3292,11 +3305,8 @@ def _revolve(

This method is a utility method, primarily for plugin and internal use.
"""
# We have to gather the wires to be revolved
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires))

# Mark that all of the wires have been used to create a revolution
self.ctx.pendingWires = []
# Get the wires to be revolved
wireSets = sortWiresByBuildOrder(self.ctx.popPendingWires())

# Revolve the wires, make a compound out of them and then fuse them
toFuse = []
Expand Down Expand Up @@ -3349,19 +3359,17 @@ def _sweep(
mode = wire

if not multisection:
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires))
wireSets = sortWiresByBuildOrder(self.ctx.popPendingWires())
for ws in wireSets:
thisObj = Solid.sweep(
ws[0], ws[1:], p, makeSolid, isFrenet, mode, transition
)
toFuse.append(thisObj)
else:
sections = self.ctx.pendingWires
sections = self.ctx.popPendingWires()
thisObj = Solid.sweep_multi(sections, p, makeSolid, isFrenet, mode)
toFuse.append(thisObj)

self.ctx.pendingWires = []

return Compound.makeCompound(toFuse)

def interpPlate(
Expand Down
74 changes: 66 additions & 8 deletions tests/test_cadquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,7 @@ def testRect(self):

def testLoft(self):
"""
Test making a lofted solid
:return:
Test making a lofted solid
"""
s = Workplane("XY").circle(4.0).workplane(5.0).rect(2.0, 2.0).loft()
self.saveModel(s)
Expand All @@ -405,10 +404,13 @@ def testLoft(self):
# the resulting loft had a split on the side, not sure why really, i expected only 3 faces
self.assertEqual(7, s.faces().size())

def testLoftWithOneWireRaisesValueError(self):
s = Workplane("XY").circle(5)
def testLoftRaisesValueError(self):
s0 = Workplane().hLine(1) # no wires
with raises(ValueError):
s0.loft()
s1 = Workplane("XY").circle(5) # one wire
with self.assertRaises(ValueError) as cm:
s.loft()
s1.loft()
err = cm.exception
self.assertEqual(str(err), "More than one wire is required")

Expand Down Expand Up @@ -551,6 +553,14 @@ def testRevolveCone(self):
self.assertEqual(2, result.vertices().size())
self.assertEqual(2, result.edges().size())

def testRevolveErrors(self):
"""
Test that revolve raises errors when used incorrectly.
"""
result = Workplane("XY").lineTo(0, 10).lineTo(5, 0)
with raises(ValueError):
result.revolve()

def testSpline(self):
"""
Tests construction of splines
Expand Down Expand Up @@ -1040,6 +1050,11 @@ def testSweep(self):

self.assertAlmostEqual(v1.getAngle(v2), math.pi / 4, 6)

# test for ValueError if pending wires is empty
w0 = Workplane().hLine(1).vLine(1)
with raises(ValueError):
w0.sweep(path)

# Test aux spine invalid input handling
with raises(ValueError):
result = (
Expand Down Expand Up @@ -1655,10 +1670,13 @@ def testCutThroughAll(self):
self.assertTrue(r.val().isValid())
self.assertEqual(r.faces().size(), 7)

# test ValueError when no solids found
w0 = Workplane().hLine(1).vLine(1).close()
# test errors
box0 = Workplane().box(1, 1, 1).faces(">Z").workplane().hLine(1)
with raises(ValueError):
w0.cutThruAll()
box0.cutThruAll()
no_box = Workplane().hLine(1).vLine(1).close()
with raises(ValueError):
no_box.cutThruAll()

def testCutToFaceOffsetNOTIMPLEMENTEDYET(self):
"""
Expand Down Expand Up @@ -4256,6 +4274,46 @@ def testFindFace(self):
with raises(ValueError):
w2.findFace(searchParents=False)

def testPopPending(self):
# test pending edges
w0 = Workplane().hLine(1)
self.assertEqual(len(w0.ctx.pendingEdges), 1)
edges = w0.ctx.popPendingEdges()
self.assertEqual(len(edges), 1)
self.assertEqual(edges[0], w0.val())
# pending edges should now be cleared
self.assertEqual(len(w0.ctx.pendingEdges), 0)

# test pending wires
w1 = Workplane().hLine(1).vLine(1).close()
wire = w1.val()
self.assertEqual(w1.ctx.pendingWires[0], wire)
pop_pending_output = w1.ctx.popPendingWires()
self.assertEqual(pop_pending_output[0], wire)
# pending wires should now be cleared
self.assertEqual(len(w1.ctx.pendingWires), 0)

# test error when empty pending edges
w2 = Workplane()
# the following 2 should not raise an exception
w2.ctx.popPendingEdges(errorOnEmpty=False)
w2.ctx.popPendingWires(errorOnEmpty=False)

# empty edges
w3 = Workplane().hLine(1).vLine(1).close()
with self.assertRaises(ValueError):
w3.ctx.popPendingEdges()

# empty wires
w4 = Workplane().circle(1).extrude(1)
with self.assertRaises(ValueError):
w4.ctx.popPendingWires()

# test via cutBlind
w5 = Workplane().circle(1).extrude(1)
with self.assertRaises(ValueError):
w5.cutBlind(-1)

def testCompSolid(self):

from OCP.BRepPrimAPI import BRepPrimAPI_MakePrism
Expand Down