From ea26397b8394ac1f72fafe6b8c61dc1ebaf4bca9 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 19 Jul 2015 10:08:20 +0300 Subject: [PATCH 01/21] added simplify API that calls freecad's removeSplitter() function to clean faces from unwanted edges --- cadquery/CQ.py | 8 ++++++++ cadquery/freecad_impl/shapes.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 20d190c..77ebc51 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -2355,3 +2355,11 @@ class Workplane(CQ): return spheres else: return self.union(spheres) + + def simplify(self): + solidRef = self.findSolid(searchStack=True, searchParents=True) + if solidRef: + t = solidRef.simplify() + return self.newObject([t]) + else: + raise ValueError("There is no solid to simplify!") diff --git a/cadquery/freecad_impl/shapes.py b/cadquery/freecad_impl/shapes.py index e0f2ecf..a992210 100644 --- a/cadquery/freecad_impl/shapes.py +++ b/cadquery/freecad_impl/shapes.py @@ -802,6 +802,9 @@ class Solid(Shape): def fuse(self, solidToJoin): return Shape.cast(self.wrapped.fuse(solidToJoin.wrapped)) + def simplify(self): + return Shape.cast(self.wrapped.removeSplitter()) + def fillet(self, radius, edgeList): """ Fillets the specified edges of this solid. From e1e8e038ed7d347bc08ff9c723f5e96a00b9950b Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 19 Jul 2015 15:44:22 +0300 Subject: [PATCH 02/21] added autoSimplify() and enabled it for union() method --- cadquery/CQ.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 77ebc51..ff939ec 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -38,6 +38,8 @@ class CQContext(object): self.firstPoint = None self.tolerance = 0.0001 # user specified tolerance + # call `simplify()` automatically for related operations + self.autoSimplifyEnabled = True class CQ(object): """ @@ -2037,11 +2039,14 @@ class Workplane(CQ): # look for parents to cut from solidRef = self.findSolid(searchStack=True, searchParents=True) if combine and solidRef is not None: - t = solidRef.fuse(newS) + r = solidRef.fuse(newS) solidRef.wrapped = newS.wrapped - return self.newObject([t]) else: - return self.newObject([newS]) + r = newS + + if self.ctx.autoSimplifyEnabled: r = r.simplify() + + return self.newObject([r]) def cut(self, toCut, combine=True): """ @@ -2363,3 +2368,15 @@ class Workplane(CQ): return self.newObject([t]) else: raise ValueError("There is no solid to simplify!") + + def autoSimplify(self, enabled=True): + """ + Enables/Disables calling of `simplify()` operation automatically. + + Using `autoSimplify(False)` to disable this feature can + improve run time of some operations substantially but may also + break some other solid operations. See `simplify()` for more + info. + """ + self.ctx.autoSimplifyEnabled = bool(enabled) + return self From 88c0195ba467c1eb48fc7d5ce6fb924b83224406 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 19 Jul 2015 15:57:43 +0300 Subject: [PATCH 03/21] enable autoSimplify for _combineWithBase --- cadquery/CQ.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index ff939ec..fdd7142 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -1992,6 +1992,8 @@ class Workplane(CQ): r = baseSolid.fuse(obj) baseSolid.wrapped = r.wrapped + if self.ctx.autoSimplifyEnabled: r = r.simplify() + return self.newObject([r]) def combine(self): From 506358db42dbf8d39ff6d290741a7f174ac6ca24 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 19 Jul 2015 17:11:51 +0300 Subject: [PATCH 04/21] enabled autoSimplify for cut and cutblind --- cadquery/CQ.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index fdd7142..2a7bea3 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -2077,8 +2077,12 @@ class Workplane(CQ): raise ValueError("Cannot cut Type '%s' " % str(type(toCut))) newS = solidRef.cut(solidToCut) + + if self.ctx.autoSimplifyEnabled: newS = newS.simplify() + if combine: solidRef.wrapped = newS.wrapped + return self.newObject([newS]) def cutBlind(self, distanceToCut): @@ -2107,6 +2111,9 @@ class Workplane(CQ): solidRef = self.findSolid() s = solidRef.cut(toCut) + + if self.ctx.autoSimplifyEnabled: s = s.simplify() + solidRef.wrapped = s.wrapped return self.newObject([s]) From acf46f4c6217044595fc4cf83871931429e83eb1 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 19 Jul 2015 17:24:16 +0300 Subject: [PATCH 05/21] enabled auto simplify for combine() --- cadquery/CQ.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 2a7bea3..5fb7e65 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -2009,6 +2009,8 @@ class Workplane(CQ): for ss in items: s = s.fuse(ss) + if self.ctx.autoSimplifyEnabled: s = s.simplify() + return self.newObject([s]) def union(self, toUnion=None, combine=True): From 8c6b3890d35b925dfe43b2e3dd69cb57a50d3f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hasan=20Yavuz=20=C3=96ZDERYA?= Date: Wed, 22 Jul 2015 23:39:11 +0300 Subject: [PATCH 06/21] added tests for simplify() method --- tests/TestCadQuery.py | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index b8e6f83..7e122d6 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1059,6 +1059,55 @@ class TestCadQuery(BaseTest): self.saveModel(s) + def testAutoSimplify(self): + """ + Tests the `simplify()` method which is called automatically. + """ + + # make a cube with a splitter edge on one of the faces + # autosimplify should remove the splitter + s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ + line(-10,0).close().extrude(10) + + self.assertEqual(6, s.faces().size()) + + # test removal of splitter caused by union operation + s = Workplane("XY").box(10,10,10).union(Workplane("XY").box(20,10,10)) + + self.assertEqual(6, s.faces().size()) + + # test removal of splitter caused by extrude+combine operation + s = Workplane("XY").box(10,10,10).faces(">Y").\ + workplane().rect(5,10,5).extrude(20) + + self.assertEqual(10, s.faces().size()) + + def testNoSimplify(self): + """ + Test the case when simplify is disabled. + """ + # test disabling autoSimplify + s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ + line(-10,0).close().autoSimplify(False).extrude(10) + self.assertEqual(7, s.faces().size()) + + s = Workplane("XY").box(10,10,10).autoSimplify(False).\ + union(Workplane("XY").box(20,10,10)) + self.assertEqual(14, s.faces().size()) + + s = Workplane("XY").box(10,10,10).faces(">Y").\ + workplane().rect(5,10,5).autoSimplify(False).extrude(20) + + self.assertEqual(12, s.faces().size()) + + def testExplicitSimplify(self): + """ + Test running of `simplify()` method explicitly. + """ + s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ + line(-10,0).close().autoSimplify(False).extrude(10).simplify() + self.assertEqual(6, s.faces().size()) + def testCup(self): """ From 268d919d1eadb0aa7882437fb0b164b35565a5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hasan=20Yavuz=20=C3=96ZDERYA?= Date: Wed, 22 Jul 2015 23:50:03 +0300 Subject: [PATCH 07/21] added documentation for `simplify()` method --- cadquery/CQ.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 5fb7e65..934e5f8 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -2373,6 +2373,22 @@ class Workplane(CQ): return self.union(spheres) def simplify(self): + """ + Simplifies the current solid by removing unwanted edges from the + faces. + + Normally you don't have to call this function. It is + automatically called after each related operation. You can + disable this behavior with `autoSimplify(False)`. In some + cases this can improve performance drastically but is + generally dis-advised since it may break some operations such + as fillet. + + Note that in some cases where lots of solid operations are + chained `simplify()` may actually improve performance since + the shape is 'simplified' at each step and thus next operation + is easier. + """ solidRef = self.findSolid(searchStack=True, searchParents=True) if solidRef: t = solidRef.simplify() From 8c719067c72acb716f49d474001ddc69d83897ad Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 2 Aug 2015 14:31:23 +0300 Subject: [PATCH 08/21] removed auto-simplify, added `clean` parameter --- cadquery/CQ.py | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 934e5f8..61d1f26 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -38,9 +38,6 @@ class CQContext(object): self.firstPoint = None self.tolerance = 0.0001 # user specified tolerance - # call `simplify()` automatically for related operations - self.autoSimplifyEnabled = True - class CQ(object): """ Provides enhanced functionality for a wrapped CAD primitive. @@ -1849,7 +1846,7 @@ class Workplane(CQ): return self.cutEach(_makeHole, True) #TODO: duplicated code with _extrude and extrude - def twistExtrude(self, distance, angleDegrees, combine=True): + def twistExtrude(self, distance, angleDegrees, combine=True, clean=True): """ Extrudes a wire in the direction normal to the plane, but also twists by the specified angle over the length of the extrusion @@ -1893,11 +1890,11 @@ class Workplane(CQ): r = r.fuse(thisObj) if combine: - return self._combineWithBase(r) + return self._combineWithBase(r, clean) else: return self.newObject([r]) - def extrude(self, distance, combine=True): + def extrude(self, distance, combine=True, clean=True): """ Use all un-extruded wires in the parent chain to create a prismatic solid. @@ -1922,11 +1919,11 @@ class Workplane(CQ): """ r = self._extrude(distance) # returns a Solid (or a compound if there were multiple) if combine: - return self._combineWithBase(r) + return self._combineWithBase(r, clean) else: return self.newObject([r]) - def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True): + def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True, clean=True): """ Use all un-revolved wires in the parent chain to create a solid. @@ -1975,11 +1972,11 @@ class Workplane(CQ): # returns a Solid (or a compound if there were multiple) r = self._revolve(angleDegrees, axisStart, axisEnd) if combine: - return self._combineWithBase(r) + return self._combineWithBase(r, clean) else: return self.newObject([r]) - def _combineWithBase(self, obj): + def _combineWithBase(self, obj, clean): """ Combines the provided object with the base solid, if one can be found. :param obj: @@ -1992,11 +1989,11 @@ class Workplane(CQ): r = baseSolid.fuse(obj) baseSolid.wrapped = r.wrapped - if self.ctx.autoSimplifyEnabled: r = r.simplify() + if clean: r = r.simplify() return self.newObject([r]) - def combine(self): + def combine(self, clean=True): """ Attempts to combine all of the items on the stack into a single item. WARNING: all of the items must be of the same type! @@ -2009,11 +2006,11 @@ class Workplane(CQ): for ss in items: s = s.fuse(ss) - if self.ctx.autoSimplifyEnabled: s = s.simplify() + if clean: s = s.simplify() return self.newObject([s]) - def union(self, toUnion=None, combine=True): + def union(self, toUnion=None, combine=True, clean=True): """ Unions all of the items on the stack of toUnion with the current solid. If there is no current solid, the items in toUnion are unioned together. @@ -2048,11 +2045,11 @@ class Workplane(CQ): else: r = newS - if self.ctx.autoSimplifyEnabled: r = r.simplify() + if clean: r = r.simplify() return self.newObject([r]) - def cut(self, toCut, combine=True): + def cut(self, toCut, combine=True, clean=True): """ Cuts the provided solid from the current solid, IE, perform a solid subtraction @@ -2080,14 +2077,14 @@ class Workplane(CQ): newS = solidRef.cut(solidToCut) - if self.ctx.autoSimplifyEnabled: newS = newS.simplify() + if clean: newS = newS.simplify() if combine: solidRef.wrapped = newS.wrapped return self.newObject([newS]) - def cutBlind(self, distanceToCut): + def cutBlind(self, distanceToCut, clean=True): """ Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. @@ -2114,7 +2111,7 @@ class Workplane(CQ): s = solidRef.cut(toCut) - if self.ctx.autoSimplifyEnabled: s = s.simplify() + if clean: s = s.simplify() solidRef.wrapped = s.wrapped return self.newObject([s]) @@ -2395,15 +2392,3 @@ class Workplane(CQ): return self.newObject([t]) else: raise ValueError("There is no solid to simplify!") - - def autoSimplify(self, enabled=True): - """ - Enables/Disables calling of `simplify()` operation automatically. - - Using `autoSimplify(False)` to disable this feature can - improve run time of some operations substantially but may also - break some other solid operations. See `simplify()` for more - info. - """ - self.ctx.autoSimplifyEnabled = bool(enabled) - return self From 29ef1937ab75a2fd2ba9460fa9f148ff7bfd0167 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 2 Aug 2015 14:37:02 +0300 Subject: [PATCH 09/21] renamed `simplify` to `clean` --- cadquery/CQ.py | 28 ++++++++++++++-------------- cadquery/freecad_impl/shapes.py | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 61d1f26..0ea6d0f 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -1989,7 +1989,7 @@ class Workplane(CQ): r = baseSolid.fuse(obj) baseSolid.wrapped = r.wrapped - if clean: r = r.simplify() + if clean: r = r.clean() return self.newObject([r]) @@ -2006,7 +2006,7 @@ class Workplane(CQ): for ss in items: s = s.fuse(ss) - if clean: s = s.simplify() + if clean: s = s.clean() return self.newObject([s]) @@ -2045,7 +2045,7 @@ class Workplane(CQ): else: r = newS - if clean: r = r.simplify() + if clean: r = r.clean() return self.newObject([r]) @@ -2077,7 +2077,7 @@ class Workplane(CQ): newS = solidRef.cut(solidToCut) - if clean: newS = newS.simplify() + if clean: newS = newS.clean() if combine: solidRef.wrapped = newS.wrapped @@ -2111,7 +2111,7 @@ class Workplane(CQ): s = solidRef.cut(toCut) - if clean: s = s.simplify() + if clean: s = s.clean() solidRef.wrapped = s.wrapped return self.newObject([s]) @@ -2369,26 +2369,26 @@ class Workplane(CQ): else: return self.union(spheres) - def simplify(self): + def clean(self): """ - Simplifies the current solid by removing unwanted edges from the + Cleans the current solid by removing unwanted edges from the faces. Normally you don't have to call this function. It is automatically called after each related operation. You can - disable this behavior with `autoSimplify(False)`. In some - cases this can improve performance drastically but is - generally dis-advised since it may break some operations such - as fillet. + disable this behavior with `clean=False` parameter if method + has any. In some cases this can improve performance + drastically but is generally dis-advised since it may break + some operations such as fillet. Note that in some cases where lots of solid operations are - chained `simplify()` may actually improve performance since + chained `clean()` may actually improve performance since the shape is 'simplified' at each step and thus next operation is easier. """ solidRef = self.findSolid(searchStack=True, searchParents=True) if solidRef: - t = solidRef.simplify() + t = solidRef.clean() return self.newObject([t]) else: - raise ValueError("There is no solid to simplify!") + raise ValueError("There is no solid to clean!") diff --git a/cadquery/freecad_impl/shapes.py b/cadquery/freecad_impl/shapes.py index a992210..1d0336f 100644 --- a/cadquery/freecad_impl/shapes.py +++ b/cadquery/freecad_impl/shapes.py @@ -802,7 +802,7 @@ class Solid(Shape): def fuse(self, solidToJoin): return Shape.cast(self.wrapped.fuse(solidToJoin.wrapped)) - def simplify(self): + def clean(self): return Shape.cast(self.wrapped.removeSplitter()) def fillet(self, radius, edgeList): From 2101161af1d97b510cf6a5ea34012b6268883256 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 2 Aug 2015 14:53:41 +0300 Subject: [PATCH 10/21] clean should be called in case of `combine=False` --- cadquery/CQ.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 0ea6d0f..cdc765f 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -1890,9 +1890,11 @@ class Workplane(CQ): r = r.fuse(thisObj) if combine: - return self._combineWithBase(r, clean) + newS = self._combineWithBase(r) else: - return self.newObject([r]) + newS = self.newObject([r]) + if clean: newS = newS.clean() + return newS def extrude(self, distance, combine=True, clean=True): """ @@ -1919,9 +1921,11 @@ class Workplane(CQ): """ r = self._extrude(distance) # returns a Solid (or a compound if there were multiple) if combine: - return self._combineWithBase(r, clean) + newS = self._combineWithBase(r) else: - return self.newObject([r]) + newS = self.newObject([r]) + if clean: newS = newS.clean() + return newS def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True, clean=True): """ @@ -1972,11 +1976,13 @@ class Workplane(CQ): # returns a Solid (or a compound if there were multiple) r = self._revolve(angleDegrees, axisStart, axisEnd) if combine: - return self._combineWithBase(r, clean) + newS = self._combineWithBase(r) else: - return self.newObject([r]) + news = self.newObject([r]) + if clean: newS = newS.clean() + return newS - def _combineWithBase(self, obj, clean): + def _combineWithBase(self, obj): """ Combines the provided object with the base solid, if one can be found. :param obj: @@ -1989,8 +1995,6 @@ class Workplane(CQ): r = baseSolid.fuse(obj) baseSolid.wrapped = r.wrapped - if clean: r = r.clean() - return self.newObject([r]) def combine(self, clean=True): From d13b0bc58b560fe53d6993cf9ca78af5c4897f01 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 2 Aug 2015 15:08:06 +0300 Subject: [PATCH 11/21] fix small type in revolve method related to clean --- cadquery/CQ.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index cdc765f..addaa7c 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -1978,7 +1978,7 @@ class Workplane(CQ): if combine: newS = self._combineWithBase(r) else: - news = self.newObject([r]) + newS = self.newObject([r]) if clean: newS = newS.clean() return newS From c0444cb0faf1df899d479a4673ac8dd8a5a6322b Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 2 Aug 2015 15:09:46 +0300 Subject: [PATCH 12/21] updated tests after 'simplify' renamed as 'clean' --- tests/TestCadQuery.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index 7e122d6..49955e0 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1059,53 +1059,53 @@ class TestCadQuery(BaseTest): self.saveModel(s) - def testAutoSimplify(self): + def testClean(self): """ - Tests the `simplify()` method which is called automatically. + Tests the `clean()` method which is called automatically. """ # make a cube with a splitter edge on one of the faces # autosimplify should remove the splitter s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ - line(-10,0).close().extrude(10) + line(-10,0).close().extrude(10, clean=True) self.assertEqual(6, s.faces().size()) # test removal of splitter caused by union operation - s = Workplane("XY").box(10,10,10).union(Workplane("XY").box(20,10,10)) + s = Workplane("XY").box(10,10,10).union(Workplane("XY").box(20,10,10), clean=True) self.assertEqual(6, s.faces().size()) # test removal of splitter caused by extrude+combine operation s = Workplane("XY").box(10,10,10).faces(">Y").\ - workplane().rect(5,10,5).extrude(20) + workplane().rect(5,10,5).extrude(20, clean=True) self.assertEqual(10, s.faces().size()) - def testNoSimplify(self): + def testNoClean(self): """ - Test the case when simplify is disabled. + Test the case when clean is disabled. """ # test disabling autoSimplify s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ - line(-10,0).close().autoSimplify(False).extrude(10) + line(-10,0).close().extrude(10, clean=False) self.assertEqual(7, s.faces().size()) - s = Workplane("XY").box(10,10,10).autoSimplify(False).\ - union(Workplane("XY").box(20,10,10)) + s = Workplane("XY").box(10,10,10).\ + union(Workplane("XY").box(20,10,10), clean=False) self.assertEqual(14, s.faces().size()) s = Workplane("XY").box(10,10,10).faces(">Y").\ - workplane().rect(5,10,5).autoSimplify(False).extrude(20) + workplane().rect(5,10,5).extrude(20, clean=False) self.assertEqual(12, s.faces().size()) - def testExplicitSimplify(self): + def testExplicitClean(self): """ - Test running of `simplify()` method explicitly. + Test running of `clean()` method explicitly. """ s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ - line(-10,0).close().autoSimplify(False).extrude(10).simplify() + line(-10,0).close().extrude(10,clean=False).clean() self.assertEqual(6, s.faces().size()) def testCup(self): From 2d1bdcad7bab4b5b5c1c46aa08e12481729adb84 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Sun, 2 Aug 2015 17:36:35 +0300 Subject: [PATCH 13/21] added clean argument for `hole` method --- cadquery/CQ.py | 12 +++++++----- tests/TestCadQuery.py | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index addaa7c..0ecac23 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -1679,7 +1679,7 @@ class Workplane(CQ): else: return -1 - def cutEach(self, fcn, useLocalCoords=False): + def cutEach(self, fcn, useLocalCoords=False, clean=True): """ Evaluates the provided function at each point on the stack (ie, eachpoint) and then cuts the result from the context solid. @@ -1699,11 +1699,13 @@ class Workplane(CQ): for cb in results: s = s.cut(cb) + if clean: s = s.clean() + ctxSolid.wrapped = s.wrapped return self.newObject([s]) #but parameter list is different so a simple function pointer wont work - def cboreHole(self, diameter, cboreDiameter, cboreDepth, depth=None): + def cboreHole(self, diameter, cboreDiameter, cboreDepth, depth=None, clean=True): """ Makes a counterbored hole for each item on the stack. @@ -1750,7 +1752,7 @@ class Workplane(CQ): r = hole.fuse(cbore) return r - return self.cutEach(_makeCbore, True) + return self.cutEach(_makeCbore, True, clean) #TODO: almost all code duplicated! #but parameter list is different so a simple function pointer wont work @@ -1804,7 +1806,7 @@ class Workplane(CQ): #TODO: almost all code duplicated! #but parameter list is different so a simple function pointer wont work - def hole(self, diameter, depth=None): + def hole(self, diameter, depth=None, clean=True): """ Makes a hole for each item on the stack. @@ -1843,7 +1845,7 @@ class Workplane(CQ): hole = Solid.makeCylinder(diameter / 2.0, depth, center, boreDir) # local coordinates! return hole - return self.cutEach(_makeHole, True) + return self.cutEach(_makeHole, True, clean) #TODO: duplicated code with _extrude and extrude def twistExtrude(self, distance, angleDegrees, combine=True, clean=True): diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index 49955e0..6de3a63 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1082,6 +1082,12 @@ class TestCadQuery(BaseTest): self.assertEqual(10, s.faces().size()) + # test removal of splitter caused by double hole operation + s = Workplane("XY").box(10,10,10).faces(">Z").workplane().\ + hole(3,5).faces(">Z").workplane().hole(3,10) + + self.assertEqual(7, s.faces().size()) + def testNoClean(self): """ Test the case when clean is disabled. From a159a71ccea3d9fb0e687c1113a6b8eff7471df9 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Mon, 3 Aug 2015 23:03:30 +0300 Subject: [PATCH 14/21] added `clean` parameter to cskHole and cutThruAll --- cadquery/CQ.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 92449cd..1f34368 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -1757,7 +1757,7 @@ class Workplane(CQ): #TODO: almost all code duplicated! #but parameter list is different so a simple function pointer wont work - def cskHole(self, diameter, cskDiameter, cskAngle, depth=None): + def cskHole(self, diameter, cskDiameter, cskAngle, depth=None, clean=True): """ Makes a countersunk hole for each item on the stack. @@ -1803,7 +1803,7 @@ class Workplane(CQ): r = hole.fuse(csk) return r - return self.cutEach(_makeCsk, True) + return self.cutEach(_makeCsk, True, clean) #TODO: almost all code duplicated! #but parameter list is different so a simple function pointer wont work @@ -2123,7 +2123,7 @@ class Workplane(CQ): solidRef.wrapped = s.wrapped return self.newObject([s]) - def cutThruAll(self, positive=False): + def cutThruAll(self, positive=False, clean=True): """ Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. @@ -2141,7 +2141,7 @@ class Workplane(CQ): if not positive: maxDim *= (-1.0) - return self.cutBlind(maxDim) + return self.cutBlind(maxDim, clean) def loft(self, filled=True, ruled=False, combine=True): """ From fd1e3076f82b34a07dac7c3c7fbf00cd3eecf246 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Mon, 3 Aug 2015 23:06:21 +0300 Subject: [PATCH 15/21] add test for 'clean' cutThruAll() --- tests/TestCadQuery.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index 6de3a63..5ca264e 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1088,6 +1088,13 @@ class TestCadQuery(BaseTest): self.assertEqual(7, s.faces().size()) + # test removal of splitter caused by cutThruAll + s = Workplane("XY").box(10,10,10).faces(">Y").workplane().\ + rect(10,5).cutBlind(-5).faces(">Z").workplane().\ + center(0,2.5).rect(5,5).cutThruAll() + + self.assertEqual(18, s.faces().size()) + def testNoClean(self): """ Test the case when clean is disabled. From 84f00a64c5aa918d7f2fa21decf8dfec07d9ef41 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Mon, 3 Aug 2015 23:07:48 +0300 Subject: [PATCH 16/21] removed `clean=True` from tests for consistency --- tests/TestCadQuery.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index 5ca264e..8e52f99 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1067,18 +1067,18 @@ class TestCadQuery(BaseTest): # make a cube with a splitter edge on one of the faces # autosimplify should remove the splitter s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ - line(-10,0).close().extrude(10, clean=True) + line(-10,0).close().extrude(10) self.assertEqual(6, s.faces().size()) # test removal of splitter caused by union operation - s = Workplane("XY").box(10,10,10).union(Workplane("XY").box(20,10,10), clean=True) + s = Workplane("XY").box(10,10,10).union(Workplane("XY").box(20,10,10)) self.assertEqual(6, s.faces().size()) # test removal of splitter caused by extrude+combine operation s = Workplane("XY").box(10,10,10).faces(">Y").\ - workplane().rect(5,10,5).extrude(20, clean=True) + workplane().rect(5,10,5).extrude(20) self.assertEqual(10, s.faces().size()) From 43f29e63f663fa9082a73dc13d67cab3ccb1c060 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Mon, 3 Aug 2015 23:12:28 +0300 Subject: [PATCH 17/21] added `clean` parameter to `box()` method --- cadquery/CQ.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 1f34368..1ca4c63 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -2243,7 +2243,7 @@ class Workplane(CQ): return Compound.makeCompound(toFuse) - def box(self, length, width, height, centered=(True, True, True), combine=True): + 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. @@ -2305,7 +2305,7 @@ class Workplane(CQ): return boxes else: #combine everything - return self.union(boxes) + return self.union(boxes, clean=clean) def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360, centered=(True, True, True), combine=True): From 605eabf2431f358fbb36859bd908c87b22835886 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Thu, 6 Aug 2015 22:33:38 +0300 Subject: [PATCH 18/21] cast to actual shape type after calling `removeSplitter()` --- cadquery/freecad_impl/shapes.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cadquery/freecad_impl/shapes.py b/cadquery/freecad_impl/shapes.py index e83e0f2..f5bad41 100644 --- a/cadquery/freecad_impl/shapes.py +++ b/cadquery/freecad_impl/shapes.py @@ -811,7 +811,11 @@ class Solid(Shape): return Shape.cast(self.wrapped.fuse(solidToJoin.wrapped)) def clean(self): - return Shape.cast(self.wrapped.removeSplitter()) + """Clean faces by removing splitter edges.""" + r = self.wrapped.removeSplitter() + # removeSplitter() returns a generic Shape type, cast to actual type of object + r = FreeCADPart.cast_to_shape(r) + return Shape.cast(r) def fillet(self, radius, edgeList): """ From 935f6e1da9df62d3d0113d35fbf4e1c7f26cc282 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Thu, 6 Aug 2015 22:39:25 +0300 Subject: [PATCH 19/21] added test for clean box --- tests/TestCadQuery.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index 8e52f99..a7d7720 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1095,6 +1095,11 @@ class TestCadQuery(BaseTest): self.assertEqual(18, s.faces().size()) + # test removal of splitter with box + s = Workplane("XY").box(5,5,5).box(10,5,2) + + self.assertEqual(14, s.faces().size()) + def testNoClean(self): """ Test the case when clean is disabled. From d7d1e5ee7b14ca103896381213ab8ef04b78e613 Mon Sep 17 00:00:00 2001 From: hyOzd Date: Thu, 6 Aug 2015 23:00:43 +0300 Subject: [PATCH 20/21] added clean parameter to `sphere()` API, although it's not supported by FreeCAD implementation --- cadquery/CQ.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 858ce1a..45474dc 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -2307,7 +2307,7 @@ class Workplane(CQ): return self.union(boxes, clean=clean) def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360, - centered=(True, True, True), combine=True): + centered=(True, True, True), combine=True, clean=True): """ Returns a 3D sphere with the specified radius for each point on the stack @@ -2373,7 +2373,7 @@ class Workplane(CQ): if not combine: return spheres else: - return self.union(spheres) + return self.union(spheres, clean=clean) def clean(self): """ From 247762f6a3b7917be4b9279d5096dd989f8319eb Mon Sep 17 00:00:00 2001 From: hyOzd Date: Thu, 6 Aug 2015 23:19:28 +0300 Subject: [PATCH 21/21] updated the docstring of `clean()` method and added `clean` parameter to each related docstring --- cadquery/CQ.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 45474dc..4596949 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -1686,6 +1686,7 @@ class Workplane(CQ): :param fcn: a function suitable for use in the eachpoint method: ie, that accepts a vector :param useLocalCoords: same as for :py:meth:`eachpoint` + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape :return: a CQ object that contains the resulting solid :raises: an error if there is not a context solid to cut from """ @@ -1717,6 +1718,7 @@ class Workplane(CQ): :type cboreDepth: float > 0 :param depth: the depth of the hole :type depth: float > 0 or None to drill thru the entire part. + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape The surface of the hole is at the current workplane plane. @@ -1768,6 +1770,7 @@ class Workplane(CQ): :type cskAngle: float > 0 :param depth: the depth of the hole :type depth: float > 0 or None to drill thru the entire part. + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape The surface of the hole is at the current workplane. @@ -1814,6 +1817,7 @@ class Workplane(CQ): :type diameter: float > 0 :param depth: the depth of the hole :type depth: float > 0 or None to drill thru the entire part. + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape The surface of the hole is at the current workplane. @@ -1864,6 +1868,7 @@ class Workplane(CQ): :param distance: the distance to extrude normal to the workplane :param angle: angline ( in degrees) to rotate through the extrusion :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. """ #group wires together into faces based on which ones are inside the others @@ -1905,6 +1910,7 @@ class Workplane(CQ): :param distance: the distance to extrude, normal to the workplane plane :type distance: float, negative means opposite the normal direction :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. extrude always *adds* material to a part. @@ -1941,6 +1947,7 @@ class Workplane(CQ): :type axisEnd: tuple, a two tuple :param combine: True to combine the resulting solid with parent solids if found. :type combine: boolean, combine with parent solid + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape :return: a CQ object with the resulting solid selected. The returned object is always a CQ object, and depends on wither combine is True, and @@ -2004,6 +2011,7 @@ class Workplane(CQ): Attempts to combine all of the items on the stack into a single item. WARNING: all of the items must be of the same type! + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape :raises: ValueError if there are no items on the stack, or if they cannot be combined :return: a CQ object with the resulting object selected """ @@ -2025,6 +2033,7 @@ class Workplane(CQ): :param toUnion: :type toUnion: a solid object, or a CQ object having a solid, + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape :raises: ValueError if there is no solid to add to in the chain :return: a CQ object with the resulting object selected """ @@ -2064,6 +2073,7 @@ class Workplane(CQ): :param toCut: object to cut :type toCut: a solid object, or a CQ object having a solid, + :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 :return: a CQ object with the resulting object selected """ @@ -2100,6 +2110,7 @@ class Workplane(CQ): :param distanceToCut: distance to extrude before cutting :type distanceToCut: float, >0 means in the positive direction of the workplane normal, <0 means in the negative direction + :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 :return: a CQ object with the resulting object selected @@ -2131,6 +2142,7 @@ class Workplane(CQ): :param boolean positive: True to cut in the positive direction, false to cut in the negative direction + :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 :return: a CQ object with the resulting object selected @@ -2257,6 +2269,7 @@ class Workplane(CQ): :param combine: should the results be combined with other solids on the stack (and each other)? :type combine: true to combine shapes, false otherwise. + :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape Centered is a tuple that describes whether the box should be centered on the x,y, and z axes. If true, the box is centered on the respective axis relative to the workplane @@ -2388,9 +2401,13 @@ class Workplane(CQ): some operations such as fillet. Note that in some cases where lots of solid operations are - chained `clean()` may actually improve performance since + chained, `clean()` may actually improve performance since the shape is 'simplified' at each step and thus next operation is easier. + + Also note that, due to limitation of the underlying engine, + `clean` may fail to produce a clean output in some cases such as + spherical faces. """ solidRef = self.findSolid(searchStack=True, searchParents=True) if solidRef: