Skip to content

Commit

Permalink
Merge ef8b320 into 6b01d78
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzncode authored Dec 22, 2021
2 parents 6b01d78 + ef8b320 commit d8eb5b6
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 21 deletions.
55 changes: 36 additions & 19 deletions cadquery/cq.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ def split(self: T, *args, **kwargs) -> T:
else [v for v in arg.vals() if isinstance(v, Shape)]
)
rv = [solid.split(*tools)]
if isinstance(arg, Workplane):
self._mergeTags(arg)

# split using the current workplane
else:
Expand Down Expand Up @@ -456,6 +458,7 @@ def add(self, obj):
self.objects.extend(obj)
elif isinstance(obj, Workplane):
self.objects.extend(obj.objects)
self._mergeTags(obj)
else:
self.objects.append(obj)
return self
Expand All @@ -471,20 +474,31 @@ def val(self) -> CQObject:

def _getTagged(self, name: str) -> "Workplane":
"""
Search the parent chain for a an object with tag == name.
Search the parent chain for an object with tag == name.
:param name: the tag to search for
:type name: string
:returns: the CQ object with tag == name
:returns: the Workplane object with tag == name
:raises: ValueError if no object tagged name
"""
rv = self.ctx.tags.get(name)

if rv is None:
raise ValueError(f"No CQ object named {name} in chain")
raise ValueError(f"No Workplane object named {name} in chain")

return rv

def _mergeTags(self: T, obj: "Workplane") -> T:
"""
Merge tags
This is automatically called when performing boolean ops.
"""

if self.ctx != obj.ctx:
self.ctx.tags = {**obj.ctx.tags, **self.ctx.tags}

return self

def toOCC(self) -> Any:
"""
Directly returns the wrapped OCCT object.
Expand Down Expand Up @@ -3250,22 +3264,23 @@ def union(
If there is no current solid, the items in toUnion are unioned together.
: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 (default True)
:param boolean glue: use a faster gluing mode for non-overlapping shapes (default False)
:param float tol: tolerance value for fuzzy bool operation mode (default None)
:type toUnion: a solid object, or a Workplane object having a solid,
:param clean: call :py:meth:`clean` afterwards to have a clean shape (default True)
:param glue: use a faster gluing mode for non-overlapping shapes (default False)
:param tol: tolerance value for fuzzy bool operation mode (default None)
:raises: ValueError if there is no solid to add to in the chain
:return: a CQ object with the resulting object selected
:return: a Workplane object with the resulting object selected
"""

# first collect all of the items together
newS: List[Shape]
if isinstance(toUnion, CQ):
if isinstance(toUnion, Workplane):
newS = cast(List[Shape], toUnion.solids().vals())
if len(newS) < 1:
raise ValueError(
"CQ object must have at least one solid on the stack to union!"
"Workplane object must have at least one solid on the stack to union!"
)
self._mergeTags(toUnion)
elif isinstance(toUnion, (Solid, Compound)):
newS = [toUnion]
else:
Expand Down Expand Up @@ -3315,19 +3330,20 @@ def cut(
Cuts the provided solid from the current solid, IE, perform a solid subtraction.
: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
:type toCut: a solid object, or a Workplane object having a solid,
:param 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
:return: a Workplane object with the resulting object selected
"""

# look for parents to cut from
solidRef = self.findSolid(searchStack=True, searchParents=True)

solidToCut: Sequence[Shape]

if isinstance(toCut, CQ):
if isinstance(toCut, Workplane):
solidToCut = _selectShapes(toCut.vals())
self._mergeTags(toCut)
elif isinstance(toCut, (Solid, Compound)):
solidToCut = (toCut,)
else:
Expand Down Expand Up @@ -3360,19 +3376,20 @@ def intersect(
Intersects the provided solid from the current solid.
:param toIntersect: object to intersect
:type toIntersect: a solid object, or a CQ object having a solid,
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
:type toIntersect: a solid object, or a Workplane object having a solid,
:param clean: call :py:meth:`clean` afterwards to have a clean shape
:raises ValueError: if there is no solid to intersect with in the chain
:return: a CQ object with the resulting object selected
:return: a Workplane object with the resulting object selected
"""

# look for parents to intersect with
solidRef = self.findSolid(searchStack=True, searchParents=True)

solidToIntersect: Sequence[Shape]

if isinstance(toIntersect, CQ):
if isinstance(toIntersect, Workplane):
solidToIntersect = _selectShapes(toIntersect.vals())
self._mergeTags(toIntersect)
elif isinstance(toIntersect, (Solid, Compound)):
solidToIntersect = (toIntersect,)
else:
Expand Down
66 changes: 64 additions & 2 deletions tests/test_cadquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3155,7 +3155,7 @@ def testExtrudeUntilFace(self):
nb_faces = wp.faces().size()
wp = wp_ref.faces(">X[1]").workplane().rect(1, 1).extrude("next")

self.assertAlmostEquals(wp_ref_extrude.val().Volume(), wp.val().Volume())
self.assertAlmostEqual(wp_ref_extrude.val().Volume(), wp.val().Volume())
self.assertTrue(wp.faces().size() - nb_faces == 4)

# Test tapered option and both option
Expand Down Expand Up @@ -3330,7 +3330,7 @@ def testCutBlindUntilFace(self):
.cutBlind("last")
)

self.assertAlmostEquals(wp_ref_regular_cut.val().Volume(), wp.val().Volume())
self.assertAlmostEqual(wp_ref_regular_cut.val().Volume(), wp.val().Volume())

wp_last = (
wp_ref.faces(">X[4]")
Expand Down Expand Up @@ -5062,3 +5062,65 @@ def circumradius(n, a):
self.assertEqual(
vs[3].toTuple(), approx((a, -a * math.tan(math.radians(45)), 0))
)

def test_MergeTags(self):

a = Workplane().box(1, 1, 1)
b = (
Workplane(origin=(1, 0, 0))
.box(1, 1, 1)
.vertices(">X and >Y and >Z")
.tag("box_vertex")
.end(2)
)
a = a.add(b)
assert a.vertices(tag="box_vertex").val().Center().toTuple() == approx(
(1.5, 0.5, 0.5)
)

a = Workplane().box(4, 4, 4)
b = Workplane(origin=(0, 0, 1)).box(2, 2, 2).faces("<Z").tag("box2_face").end()
a = a.cut(b)
assert a.val().Volume() == approx(4 ** 3 - 2 ** 3)
a = a.faces(tag="box2_face").wires().toPending().extrude(4)
assert a.val().Volume() == approx(4 ** 3 + 2 ** 3)

a = Workplane().sphere(2)
b = Workplane().cylinder(4, 1).tag("cyl")
a = a.intersect(b)
assert len(a.solids(tag="cyl").val().Solids()) == 1

a = Workplane().box(4, 4, 4)
b = (
Workplane()
.box(2, 5, 5, centered=(False, True, True))
.faces(">X")
.workplane()
.tag("splitter")
.end(2)
)
a = a.split(b)
a = a.solids("<X")
assert a.val().Volume() == approx((4 ** 3) / 2.0)
a = a.workplaneFromTagged("splitter").rect(4, 4).extrude(until="next")
assert a.val().Volume() == approx((4 ** 3))

a = Workplane().box(4, 4, 4)
b = Workplane(origin=(0, 0, 3)).box(2, 2, 2).faces(">Z").tag("box2_face").end()
a = a.union(b)
a = a.faces(tag="box2_face").workplane(offset=0.5).box(1, 1, 1)
assert a.val().Volume() == approx(4 ** 3 + 2 ** 3 + 1)

# tag name conflict; keep tag from left side of boolean
a = Workplane().box(1, 1, 1).faces(">Z").workplane().tag("zface").end(2)
b = (
Workplane(origin=(1, 0, 0))
.box(1, 1, 2)
.faces(">Z")
.workplane()
.tag("zface")
.end(2)
)
a = a.union(b)
a = a.workplaneFromTagged("zface").circle(0.2)
assert a.edges("%CIRCLE").val().Center().toTuple() == approx((0, 0, 0.5))

0 comments on commit d8eb5b6

Please sign in to comment.