Skip to content

Commit d17d27d

Browse files
authored
Merge pull request #312 from CadQuery/adam-urbanczy-boolop-fix
Bool op refactoring
2 parents 8062b10 + 0276a8f commit d17d27d

File tree

3 files changed

+131
-26
lines changed

3 files changed

+131
-26
lines changed

cadquery/cq.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2700,8 +2700,9 @@ def combine(self, clean=True):
27002700
"""
27012701
items = list(self.objects)
27022702
s = items.pop(0)
2703-
for ss in items:
2704-
s = s.fuse(ss)
2703+
2704+
if items:
2705+
s.fuse(*items)
27052706

27062707
if clean:
27072708
s = s.clean()
@@ -2722,26 +2723,25 @@ def union(self, toUnion=None, clean=True):
27222723

27232724
# first collect all of the items together
27242725
if type(toUnion) == CQ or type(toUnion) == Workplane:
2725-
solids = toUnion.solids().vals()
2726-
if len(solids) < 1:
2726+
newS = toUnion.solids().vals()
2727+
if len(newS) < 1:
27272728
raise ValueError(
27282729
"CQ object must have at least one solid on the stack to union!"
27292730
)
2730-
newS = solids.pop(0)
2731-
for s in solids:
2732-
newS = newS.fuse(s)
27332731
elif type(toUnion) in (Solid, Compound):
2734-
newS = toUnion
2732+
newS = (toUnion,)
27352733
else:
27362734
raise ValueError("Cannot union type '{}'".format(type(toUnion)))
27372735

27382736
# now combine with existing solid, if there is one
27392737
# look for parents to cut from
27402738
solidRef = self.findSolid(searchStack=True, searchParents=True)
27412739
if solidRef is not None:
2742-
r = solidRef.fuse(newS)
2740+
r = solidRef.fuse(*newS)
2741+
elif len(newS) > 1:
2742+
r = newS.pop(0).fuse(*newS)
27432743
else:
2744-
r = newS
2744+
r = newS[0]
27452745

27462746
if clean:
27472747
r = r.clean()
@@ -2766,13 +2766,13 @@ def cut(self, toCut, clean=True):
27662766
raise ValueError("Cannot find solid to cut from")
27672767
solidToCut = None
27682768
if type(toCut) == CQ or type(toCut) == Workplane:
2769-
solidToCut = toCut.val()
2769+
solidToCut = toCut.vals()
27702770
elif type(toCut) in (Solid, Compound):
2771-
solidToCut = toCut
2771+
solidToCut = (toCut,)
27722772
else:
27732773
raise ValueError("Cannot cut type '{}'".format(type(toCut)))
27742774

2775-
newS = solidRef.cut(solidToCut)
2775+
newS = solidRef.cut(*solidToCut)
27762776

27772777
if clean:
27782778
newS = newS.clean()
@@ -2798,13 +2798,13 @@ def intersect(self, toIntersect, clean=True):
27982798
solidToIntersect = None
27992799

28002800
if isinstance(toIntersect, CQ):
2801-
solidToIntersect = toIntersect.val()
2801+
solidToIntersect = toIntersect.vals()
28022802
elif isinstance(toIntersect, Solid) or isinstance(toIntersect, Compound):
2803-
solidToIntersect = toIntersect
2803+
solidToIntersect = (toIntersect,)
28042804
else:
28052805
raise ValueError("Cannot intersect type '{}'".format(type(toIntersect)))
28062806

2807-
newS = solidRef.intersect(solidToIntersect)
2807+
newS = solidRef.intersect(*solidToIntersect)
28082808

28092809
if clean:
28102810
newS = newS.clean()

cadquery/occ_impl/shapes.py

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
topods_Solid,
7676
)
7777

78-
from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Builder
78+
from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Builder, TopoDS_Iterator
7979

8080
from OCC.Core.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse # geometry construction
8181
from OCC.Core.GCE2d import GCE2d_MakeSegment
@@ -587,30 +587,59 @@ def transformGeometry(self, tMatrix):
587587
def __hash__(self):
588588
return self.hashCode()
589589

590-
def cut(self, toCut):
590+
def _bool_op(self, args, tools, op):
591+
"""
592+
Generic boolean operation
593+
"""
594+
595+
arg = TopTools_ListOfShape()
596+
for obj in args:
597+
arg.Append(obj.wrapped)
598+
599+
tool = TopTools_ListOfShape()
600+
for obj in tools:
601+
tool.Append(obj.wrapped)
602+
603+
op.SetArguments(arg)
604+
op.SetTools(tool)
605+
606+
op.SetRunParallel(True)
607+
op.Build()
608+
609+
return Shape.cast(op.Shape())
610+
611+
def cut(self, *toCut):
591612
"""
592613
Remove a shape from another one
593614
"""
594-
return Shape.cast(BRepAlgoAPI_Cut(self.wrapped, toCut.wrapped).Shape())
595615

596-
def fuse(self, toFuse):
616+
cut_op = BRepAlgoAPI_Cut()
617+
618+
return self._bool_op((self,), toCut, cut_op)
619+
620+
def fuse(self, *toFuse):
597621
"""
598622
Fuse shapes together
599623
"""
600624

601-
fuse_op = BRepAlgoAPI_Fuse(self.wrapped, toFuse.wrapped)
625+
fuse_op = BRepAlgoAPI_Fuse()
626+
# fuse_op.SetFuzzyValue(TOLERANCE)
627+
628+
rv = self._bool_op((self,), toFuse, fuse_op)
629+
602630
fuse_op.RefineEdges()
603631
fuse_op.FuseEdges()
604-
# fuse_op.SetFuzzyValue(TOLERANCE)
605-
fuse_op.Build()
606632

607-
return Shape.cast(fuse_op.Shape())
633+
return rv
608634

609-
def intersect(self, toIntersect):
635+
def intersect(self, *toIntersect):
610636
"""
611637
Construct shape intersection
612638
"""
613-
return Shape.cast(BRepAlgoAPI_Common(self.wrapped, toIntersect.wrapped).Shape())
639+
640+
intersect_op = BRepAlgoAPI_Common()
641+
642+
return self._bool_op((self,), toIntersect, intersect_op)
614643

615644
def _repr_html_(self):
616645
"""
@@ -1939,6 +1968,51 @@ def makeText(
19391968

19401969
return cls(text_3d.Shape()).transformShape(position.rG)
19411970

1971+
def __iter__(self):
1972+
"""
1973+
Iterate over subshapes.
1974+
1975+
"""
1976+
1977+
it = TopoDS_Iterator(self.wrapped)
1978+
1979+
while it.More():
1980+
yield Shape.cast(it.Value())
1981+
it.Next()
1982+
1983+
def cut(self, *toCut):
1984+
"""
1985+
Remove a shape from another one
1986+
"""
1987+
1988+
cut_op = BRepAlgoAPI_Cut()
1989+
1990+
return self._bool_op(self, toCut, cut_op)
1991+
1992+
def fuse(self, *toFuse):
1993+
"""
1994+
Fuse shapes together
1995+
"""
1996+
1997+
fuse_op = BRepAlgoAPI_Fuse()
1998+
# fuse_op.SetFuzzyValue(TOLERANCE)
1999+
2000+
rv = self._bool_op(self, toFuse, fuse_op)
2001+
2002+
# fuse_op.RefineEdges()
2003+
# fuse_op.FuseEdges()
2004+
2005+
return rv
2006+
2007+
def intersect(self, *toIntersect):
2008+
"""
2009+
Construct shape intersection
2010+
"""
2011+
2012+
intersect_op = BRepAlgoAPI_Common()
2013+
2014+
return self._bool_op(self, toIntersect, intersect_op)
2015+
19422016

19432017
def sortWiresByBuildOrder(wireList, result={}):
19442018
"""Tries to determine how wires should be combined into faces.

tests/test_cadquery.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,9 @@ def testCut(self):
12271227

12281228
self.assertEqual(10, resS.faces().size())
12291229

1230+
with self.assertRaises(ValueError):
1231+
currentS.cut(toCut.faces().val())
1232+
12301233
def testIntersect(self):
12311234
"""
12321235
Tests the intersect function.
@@ -1245,6 +1248,15 @@ def testIntersect(self):
12451248
self.assertEqual(6, resS.faces().size())
12461249
self.assertAlmostEqual(resS.val().Volume(), 0.5)
12471250

1251+
b1 = Workplane("XY").box(1, 1, 1)
1252+
b2 = Workplane("XY", origin=(0, 0, 0.5)).box(1, 1, 1)
1253+
resS = b1.intersect(b2)
1254+
1255+
self.assertAlmostEqual(resS.val().Volume(), 0.5)
1256+
1257+
with self.assertRaises(ValueError):
1258+
b1.intersect(b2.faces().val())
1259+
12481260
def testBoundingBox(self):
12491261
"""
12501262
Tests the boudingbox center of a model
@@ -2037,6 +2049,9 @@ def testUnions(self):
20372049

20382050
self.assertEqual(11, resS.faces().size())
20392051

2052+
with self.assertRaises(ValueError):
2053+
resS.union(toUnion.faces().val())
2054+
20402055
def testCombine(self):
20412056
s = Workplane(Plane.XY())
20422057
objects1 = s.rect(2.0, 2.0).extrude(0.5).faces(">Z").rect(1.0, 1.0).extrude(0.5)
@@ -3358,3 +3373,19 @@ def testMakeHelix(self):
33583373

33593374
bb = obj.BoundingBox()
33603375
self.assertAlmostEqual(bb.zlen, h, 1)
3376+
3377+
def testUnionCompound(self):
3378+
3379+
box1 = Workplane("XY").box(10, 20, 30)
3380+
box2 = Workplane("YZ").box(10, 20, 30)
3381+
shape_to_cut = Workplane("XY").box(15, 15, 15).translate((8, 8, 8))
3382+
3383+
list_of_shapes = []
3384+
for o in box1.all():
3385+
list_of_shapes.extend(o.vals())
3386+
for o in box2.all():
3387+
list_of_shapes.extend(o.vals())
3388+
3389+
obj = Workplane("XY").newObject(list_of_shapes).cut(shape_to_cut)
3390+
3391+
assert obj.val().isValid()

0 commit comments

Comments
 (0)