From 5328bc2a8cec50fafe061da32d1f4b2c069bc36c Mon Sep 17 00:00:00 2001 From: Lorenz Neureuter Date: Wed, 22 Dec 2021 10:50:40 -0500 Subject: [PATCH 1/4] Merge tags on boolean ops --- cadquery/cq.py | 54 ++++++++++++++++++++++------------ tests/test_cadquery.py | 66 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 21 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index a09259282..5d241b6bc 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -307,6 +307,7 @@ def split(self: T, *args, **kwargs) -> T: else [v for v in arg.vals() if isinstance(v, Shape)] ) rv = [solid.split(*tools)] + self._mergeTags(arg) # split using the current workplane else: @@ -456,6 +457,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 @@ -471,20 +473,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, obj: T) -> 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. @@ -3250,22 +3263,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: @@ -3315,10 +3329,10 @@ 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 @@ -3326,8 +3340,9 @@ def cut( 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: @@ -3360,10 +3375,10 @@ 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 @@ -3371,8 +3386,9 @@ def intersect( 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: diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index d9f7d538d..f19c1b856 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -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 @@ -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]") @@ -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("X") + .workplane() + .tag("splitter") + .end(2) + ) + a = a.split(b) + a = a.solids("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)) From 83d580dcd77b12372be7ba54c266ac31361e760e Mon Sep 17 00:00:00 2001 From: Lorenz Neureuter Date: Wed, 22 Dec 2021 12:55:26 -0500 Subject: [PATCH 2/4] Try mypy fix --- cadquery/cq.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index 5d241b6bc..2f1b841e8 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -307,7 +307,8 @@ def split(self: T, *args, **kwargs) -> T: else [v for v in arg.vals() if isinstance(v, Shape)] ) rv = [solid.split(*tools)] - self._mergeTags(arg) + if isinstance(arg, Workplane): + self._mergeTags(arg) # split using the current workplane else: @@ -486,7 +487,7 @@ def _getTagged(self, name: str) -> "Workplane": return rv - def _mergeTags(self, obj: T) -> T: + def _mergeTags(self, obj: T) -> "Workplane": """ Merge tags From 8fc3a5d388bed6568ad6c1bbbaab0e3996bb8184 Mon Sep 17 00:00:00 2001 From: AU Date: Wed, 22 Dec 2021 21:01:36 +0100 Subject: [PATCH 3/4] Improve type annotations --- cadquery/cq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index 2f1b841e8..b17d906f0 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -487,7 +487,7 @@ def _getTagged(self, name: str) -> "Workplane": return rv - def _mergeTags(self, obj: T) -> "Workplane": + def _mergeTags(self: T, obj: T) -> T: """ Merge tags From ef8b32042ddbd6afd388810ba602936da3898489 Mon Sep 17 00:00:00 2001 From: AU Date: Wed, 22 Dec 2021 21:39:08 +0100 Subject: [PATCH 4/4] Another try --- cadquery/cq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index b17d906f0..bdadfe4a2 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -487,7 +487,7 @@ def _getTagged(self, name: str) -> "Workplane": return rv - def _mergeTags(self: T, obj: T) -> T: + def _mergeTags(self: T, obj: "Workplane") -> T: """ Merge tags