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/5] 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 e49b539eb77674388a2508ed9d250a973ea3856c Mon Sep 17 00:00:00 2001 From: bsilvereagle Date: Tue, 26 Apr 2016 11:37:26 -0400 Subject: [PATCH 2/5] Update README.md to link to FreeCAD Github repo Changed the FreeCAD download link from the sourceforge.net page to the FreeCAD Github repo releases page --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75c50da..e5acc25 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ Installing -- FreeStanding Installation Use these steps if you would like to write CadQuery scripts as a python API. In this case, FreeCAD is used only as a CAD kernel. -1. install FreeCAD, version 0.12 or greater for your platform. http://sourceforge.net/projects/free-cad/. +1. install FreeCAD, version 0.12 or greater for your platform. https://github.com/FreeCAD/FreeCAD/releases. 2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own, preferably one that has virtualenv available. To use FreeCAD from any python interpreter, just append the FreeCAD From 7f29a141accb63869a05e07cf242e5c800968452 Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Tue, 26 Apr 2016 12:41:29 -0400 Subject: [PATCH 3/5] Updated minimum FreeCAD version requirement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5acc25..befb338 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ Installing -- FreeStanding Installation Use these steps if you would like to write CadQuery scripts as a python API. In this case, FreeCAD is used only as a CAD kernel. -1. install FreeCAD, version 0.12 or greater for your platform. https://github.com/FreeCAD/FreeCAD/releases. +1. install FreeCAD, version 0.15 or greater for your platform. https://github.com/FreeCAD/FreeCAD/releases. 2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own, preferably one that has virtualenv available. To use FreeCAD from any python interpreter, just append the FreeCAD From 822af6c7f5b54d9d188e1950158bfdef2514f964 Mon Sep 17 00:00:00 2001 From: Jeremy Mack Wright Date: Tue, 26 Apr 2016 20:11:12 -0400 Subject: [PATCH 4/5] 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 5/5] 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)