Skip to content
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
32 changes: 16 additions & 16 deletions cadquery/cq.py
Original file line number Diff line number Diff line change
Expand Up @@ -2700,8 +2700,9 @@ def combine(self, clean=True):
"""
items = list(self.objects)
s = items.pop(0)
for ss in items:
s = s.fuse(ss)

if items:
s.fuse(*items)

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

# first collect all of the items together
if type(toUnion) == CQ or type(toUnion) == Workplane:
solids = toUnion.solids().vals()
if len(solids) < 1:
newS = toUnion.solids().vals()
if len(newS) < 1:
raise ValueError(
"CQ object must have at least one solid on the stack to union!"
)
newS = solids.pop(0)
for s in solids:
newS = newS.fuse(s)
elif type(toUnion) in (Solid, Compound):
newS = toUnion
newS = (toUnion,)
else:
raise ValueError("Cannot union type '{}'".format(type(toUnion)))

# now combine with existing solid, if there is one
# look for parents to cut from
solidRef = self.findSolid(searchStack=True, searchParents=True)
if solidRef is not None:
r = solidRef.fuse(newS)
r = solidRef.fuse(*newS)
elif len(newS) > 1:
r = newS.pop(0).fuse(*newS)
else:
r = newS
r = newS[0]

if clean:
r = r.clean()
Expand All @@ -2766,13 +2766,13 @@ def cut(self, toCut, clean=True):
raise ValueError("Cannot find solid to cut from")
solidToCut = None
if type(toCut) == CQ or type(toCut) == Workplane:
solidToCut = toCut.val()
solidToCut = toCut.vals()
elif type(toCut) in (Solid, Compound):
solidToCut = toCut
solidToCut = (toCut,)
else:
raise ValueError("Cannot cut type '{}'".format(type(toCut)))

newS = solidRef.cut(solidToCut)
newS = solidRef.cut(*solidToCut)

if clean:
newS = newS.clean()
Expand All @@ -2798,13 +2798,13 @@ def intersect(self, toIntersect, clean=True):
solidToIntersect = None

if isinstance(toIntersect, CQ):
solidToIntersect = toIntersect.val()
solidToIntersect = toIntersect.vals()
elif isinstance(toIntersect, Solid) or isinstance(toIntersect, Compound):
solidToIntersect = toIntersect
solidToIntersect = (toIntersect,)
else:
raise ValueError("Cannot intersect type '{}'".format(type(toIntersect)))

newS = solidRef.intersect(solidToIntersect)
newS = solidRef.intersect(*solidToIntersect)

if clean:
newS = newS.clean()
Expand Down
94 changes: 84 additions & 10 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
topods_Solid,
)

from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Builder
from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Builder, TopoDS_Iterator

from OCC.Core.GC import GC_MakeArcOfCircle, GC_MakeArcOfEllipse # geometry construction
from OCC.Core.GCE2d import GCE2d_MakeSegment
Expand Down Expand Up @@ -587,30 +587,59 @@ def transformGeometry(self, tMatrix):
def __hash__(self):
return self.hashCode()

def cut(self, toCut):
def _bool_op(self, args, tools, op):
"""
Generic boolean operation
"""

arg = TopTools_ListOfShape()
for obj in args:
arg.Append(obj.wrapped)

tool = TopTools_ListOfShape()
for obj in tools:
tool.Append(obj.wrapped)

op.SetArguments(arg)
op.SetTools(tool)

op.SetRunParallel(True)
op.Build()

return Shape.cast(op.Shape())

def cut(self, *toCut):
"""
Remove a shape from another one
"""
return Shape.cast(BRepAlgoAPI_Cut(self.wrapped, toCut.wrapped).Shape())

def fuse(self, toFuse):
cut_op = BRepAlgoAPI_Cut()

return self._bool_op((self,), toCut, cut_op)

def fuse(self, *toFuse):
"""
Fuse shapes together
"""

fuse_op = BRepAlgoAPI_Fuse(self.wrapped, toFuse.wrapped)
fuse_op = BRepAlgoAPI_Fuse()
# fuse_op.SetFuzzyValue(TOLERANCE)

rv = self._bool_op((self,), toFuse, fuse_op)

fuse_op.RefineEdges()
fuse_op.FuseEdges()
# fuse_op.SetFuzzyValue(TOLERANCE)
fuse_op.Build()

return Shape.cast(fuse_op.Shape())
return rv

def intersect(self, toIntersect):
def intersect(self, *toIntersect):
"""
Construct shape intersection
"""
return Shape.cast(BRepAlgoAPI_Common(self.wrapped, toIntersect.wrapped).Shape())

intersect_op = BRepAlgoAPI_Common()

return self._bool_op((self,), toIntersect, intersect_op)

def _repr_html_(self):
"""
Expand Down Expand Up @@ -1939,6 +1968,51 @@ def makeText(

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

def __iter__(self):
"""
Iterate over subshapes.

"""

it = TopoDS_Iterator(self.wrapped)

while it.More():
yield Shape.cast(it.Value())
it.Next()

def cut(self, *toCut):
"""
Remove a shape from another one
"""

cut_op = BRepAlgoAPI_Cut()

return self._bool_op(self, toCut, cut_op)

def fuse(self, *toFuse):
"""
Fuse shapes together
"""

fuse_op = BRepAlgoAPI_Fuse()
# fuse_op.SetFuzzyValue(TOLERANCE)

rv = self._bool_op(self, toFuse, fuse_op)

# fuse_op.RefineEdges()
# fuse_op.FuseEdges()

return rv

def intersect(self, *toIntersect):
"""
Construct shape intersection
"""

intersect_op = BRepAlgoAPI_Common()

return self._bool_op(self, toIntersect, intersect_op)


def sortWiresByBuildOrder(wireList, result={}):
"""Tries to determine how wires should be combined into faces.
Expand Down
31 changes: 31 additions & 0 deletions tests/test_cadquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,9 @@ def testCut(self):

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

with self.assertRaises(ValueError):
currentS.cut(toCut.faces().val())

def testIntersect(self):
"""
Tests the intersect function.
Expand All @@ -1245,6 +1248,15 @@ def testIntersect(self):
self.assertEqual(6, resS.faces().size())
self.assertAlmostEqual(resS.val().Volume(), 0.5)

b1 = Workplane("XY").box(1, 1, 1)
b2 = Workplane("XY", origin=(0, 0, 0.5)).box(1, 1, 1)
resS = b1.intersect(b2)

self.assertAlmostEqual(resS.val().Volume(), 0.5)

with self.assertRaises(ValueError):
b1.intersect(b2.faces().val())

def testBoundingBox(self):
"""
Tests the boudingbox center of a model
Expand Down Expand Up @@ -2037,6 +2049,9 @@ def testUnions(self):

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

with self.assertRaises(ValueError):
resS.union(toUnion.faces().val())

def testCombine(self):
s = Workplane(Plane.XY())
objects1 = s.rect(2.0, 2.0).extrude(0.5).faces(">Z").rect(1.0, 1.0).extrude(0.5)
Expand Down Expand Up @@ -3358,3 +3373,19 @@ def testMakeHelix(self):

bb = obj.BoundingBox()
self.assertAlmostEqual(bb.zlen, h, 1)

def testUnionCompound(self):

box1 = Workplane("XY").box(10, 20, 30)
box2 = Workplane("YZ").box(10, 20, 30)
shape_to_cut = Workplane("XY").box(15, 15, 15).translate((8, 8, 8))

list_of_shapes = []
for o in box1.all():
list_of_shapes.extend(o.vals())
for o in box2.all():
list_of_shapes.extend(o.vals())

obj = Workplane("XY").newObject(list_of_shapes).cut(shape_to_cut)

assert obj.val().isValid()