From 3e127263336d2a9260b075d3849175d2793bbc1b Mon Sep 17 00:00:00 2001 From: Jeremy Mack Wright Date: Mon, 25 Apr 2016 22:38:28 -0400 Subject: [PATCH 1/3] Rough draft of the sweep operation implementation. No unit tests yet. --- cadquery/cq.py | 40 +++++++++++++++++++++++++++++++++ cadquery/freecad_impl/shapes.py | 22 ++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/cadquery/cq.py b/cadquery/cq.py index 7293813..4f87f64 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -2055,6 +2055,24 @@ class Workplane(CQ): if clean: newS = newS.clean() return newS + def sweep(self, path, combine=True, clean=True): + """ + Use all un-extruded wires in the parent chain to create a swept solid. + + :param path: A wire along which the pending wires will be swept + :param boolean combine: True to combine the resulting solid with parent solids if found. + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape + :return: a CQ object with the resulting solid selected. + """ + + r = self._sweep(path.wire()) # returns a Solid (or a compound if there were multiple) + if combine: + newS = self._combineWithBase(r) + else: + newS = self.newObject([r]) + if clean: newS = newS.clean() + return newS + def _combineWithBase(self, obj): """ Combines the provided object with the base solid, if one can be found. @@ -2318,6 +2336,28 @@ class Workplane(CQ): return Compound.makeCompound(toFuse) + def _sweep(self, path): + """ + Makes a swept solid from an existing set of pending wires. + + :param path: A wire along which the pending wires will be swept + :return:a FreeCAD solid, suitable for boolean operations + """ + + # group wires together into faces based on which ones are inside the others + # result is a list of lists + s = time.time() + wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, []) + # print "sorted wires in %d sec" % ( time.time() - s ) + self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion + + toFuse = [] + for ws in wireSets: + thisObj = Solid.sweep(ws[0], ws[1:], path) + toFuse.append(thisObj) + + return Compound.makeCompound(toFuse) + def box(self, length, width, height, centered=(True, True, True), combine=True, clean=True): """ Return a 3d box with specified dimensions for each object on the stack. diff --git a/cadquery/freecad_impl/shapes.py b/cadquery/freecad_impl/shapes.py index 659d4d1..4c823f1 100644 --- a/cadquery/freecad_impl/shapes.py +++ b/cadquery/freecad_impl/shapes.py @@ -905,6 +905,28 @@ class Solid(Shape): return Shape.cast(result) + @classmethod + def sweep(cls, outerWire, innerWires, path): + """ + Attempt to sweep the list of wires into a prismatic solid along the provided path + + :param outerWire: the outermost wire + :param innerWires: a list of inner wires + :param path: The wire to sweep the face resulting from the wires over + :return: a Solid object + """ + + # FreeCAD allows this in one operation, but others might not + freeCADWires = [outerWire.wrapped] + for w in innerWires: + freeCADWires.append(w.wrapped) + + # f = FreeCADPart.Face(freeCADWires) + wire = FreeCADPart.Wire([path.val().wrapped]) + result = wire.makePipeShell(freeCADWires, True, True) + + return Shape.cast(result) + def tessellate(self, tolerance): return self.wrapped.tessellate(tolerance) From 822af6c7f5b54d9d188e1950158bfdef2514f964 Mon Sep 17 00:00:00 2001 From: Jeremy Mack Wright Date: Tue, 26 Apr 2016 20:11:12 -0400 Subject: [PATCH 2/3] Finished sweep operation and added tests. --- cadquery/cq.py | 8 +-- cadquery/freecad_impl/shapes.py | 4 +- ...G => parametric-pillowblock-screencap.png} | Bin tests/TestCadQuery.py | 54 ++++++++++++++++++ 4 files changed, 60 insertions(+), 6 deletions(-) rename doc/_static/{PillowBlock.PNG => parametric-pillowblock-screencap.png} (100%) diff --git a/cadquery/cq.py b/cadquery/cq.py index 4f87f64..0bf1548 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -2055,7 +2055,7 @@ class Workplane(CQ): if clean: newS = newS.clean() return newS - def sweep(self, path, combine=True, clean=True): + def sweep(self, path, makeSolid=True, isFrenet=False, combine=True, clean=True): """ Use all un-extruded wires in the parent chain to create a swept solid. @@ -2065,7 +2065,7 @@ class Workplane(CQ): :return: a CQ object with the resulting solid selected. """ - r = self._sweep(path.wire()) # returns a Solid (or a compound if there were multiple) + r = self._sweep(path.wire(), makeSolid, isFrenet) # returns a Solid (or a compound if there were multiple) if combine: newS = self._combineWithBase(r) else: @@ -2336,7 +2336,7 @@ class Workplane(CQ): return Compound.makeCompound(toFuse) - def _sweep(self, path): + def _sweep(self, path, makeSolid=True, isFrenet=False): """ Makes a swept solid from an existing set of pending wires. @@ -2353,7 +2353,7 @@ class Workplane(CQ): toFuse = [] for ws in wireSets: - thisObj = Solid.sweep(ws[0], ws[1:], path) + thisObj = Solid.sweep(ws[0], ws[1:], path, makeSolid, isFrenet) toFuse.append(thisObj) return Compound.makeCompound(toFuse) diff --git a/cadquery/freecad_impl/shapes.py b/cadquery/freecad_impl/shapes.py index 4c823f1..e486709 100644 --- a/cadquery/freecad_impl/shapes.py +++ b/cadquery/freecad_impl/shapes.py @@ -906,7 +906,7 @@ class Solid(Shape): return Shape.cast(result) @classmethod - def sweep(cls, outerWire, innerWires, path): + def sweep(cls, outerWire, innerWires, path, makeSolid=True, isFrenet=False): """ Attempt to sweep the list of wires into a prismatic solid along the provided path @@ -923,7 +923,7 @@ class Solid(Shape): # f = FreeCADPart.Face(freeCADWires) wire = FreeCADPart.Wire([path.val().wrapped]) - result = wire.makePipeShell(freeCADWires, True, True) + result = wire.makePipeShell(freeCADWires, makeSolid, isFrenet) return Shape.cast(result) diff --git a/doc/_static/PillowBlock.PNG b/doc/_static/parametric-pillowblock-screencap.png similarity index 100% rename from doc/_static/PillowBlock.PNG rename to doc/_static/parametric-pillowblock-screencap.png diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index f8b9b2a..7947ee1 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -349,6 +349,60 @@ class TestCadQuery(BaseTest): self.assertEqual(2, result.vertices().size()) self.assertEqual(3, result.edges().size()) + def testSweep(self): + """ + Tests the operation of sweeping a wire(s) along a path + """ + pts = [ + (0, 1), + (1, 2), + (2, 4) + ] + + # Spline path + path = Workplane("XZ").spline(pts) + + # Test defaults + result = Workplane("XY").circle(1.0).sweep(path) + self.assertEqual(3, result.faces().size()) + self.assertEqual(3, result.edges().size()) + + # Test with makeSolid False + result = Workplane("XY").circle(1.0).sweep(path, makeSolid=False) + self.assertEqual(1, result.faces().size()) + self.assertEqual(3, result.edges().size()) + + # Test with isFrenet True + result = Workplane("XY").circle(1.0).sweep(path, isFrenet=True) + self.assertEqual(3, result.faces().size()) + self.assertEqual(3, result.edges().size()) + + # Test with makeSolid False and isFrenet True + result = Workplane("XY").circle(1.0).sweep(path, makeSolid=False, isFrenet=True) + self.assertEqual(1, result.faces().size()) + self.assertEqual(3, result.edges().size()) + + # Test rectangle with defaults + result = Workplane("XY").rect(1.0, 1.0).sweep(path) + self.assertEqual(6, result.faces().size()) + self.assertEqual(12, result.edges().size()) + + # Polyline path + path = Workplane("XZ").polyline(pts) + + # Test defaults + result = Workplane("XY").circle(0.1).sweep(path) + self.assertEqual(5, result.faces().size()) + self.assertEqual(7, result.edges().size()) + + # Arc path + path = Workplane("XZ").threePointArc((1.0, 1.5),(0.0, 1.0)) + + # Test defaults + result = Workplane("XY").circle(0.1).sweep(path) + self.assertEqual(3, result.faces().size()) + self.assertEqual(3, result.edges().size()) + def testTwistExtrude(self): """ Tests extrusion while twisting through an angle. From 02dd0f2508c702f2717ffe94373873d1b6dc5445 Mon Sep 17 00:00:00 2001 From: Jeremy Mack Wright Date: Tue, 26 Apr 2016 20:20:18 -0400 Subject: [PATCH 3/3] Changed where val was being called to keep the implementation cosistent with what has been done before. --- cadquery/cq.py | 2 +- cadquery/freecad_impl/shapes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index 0bf1548..2ac9d7e 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -2353,7 +2353,7 @@ class Workplane(CQ): toFuse = [] for ws in wireSets: - thisObj = Solid.sweep(ws[0], ws[1:], path, makeSolid, isFrenet) + thisObj = Solid.sweep(ws[0], ws[1:], path.val(), makeSolid, isFrenet) toFuse.append(thisObj) return Compound.makeCompound(toFuse) diff --git a/cadquery/freecad_impl/shapes.py b/cadquery/freecad_impl/shapes.py index e486709..4cec80a 100644 --- a/cadquery/freecad_impl/shapes.py +++ b/cadquery/freecad_impl/shapes.py @@ -922,7 +922,7 @@ class Solid(Shape): freeCADWires.append(w.wrapped) # f = FreeCADPart.Face(freeCADWires) - wire = FreeCADPart.Wire([path.val().wrapped]) + wire = FreeCADPart.Wire([path.wrapped]) result = wire.makePipeShell(freeCADWires, makeSolid, isFrenet) return Shape.cast(result)