From d1ced40c4e4c61847856bf5beb63c46b76a3bb52 Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Fri, 20 Dec 2019 20:54:59 +1030 Subject: [PATCH 01/11] Added tags --- cadquery/cq.py | 62 +++++++++++++++++++++++++++++++++++++++-- tests/test_cadquery.py | 57 +++++++++++++++++++++++++++++++++++++ tests/test_selectors.py | 2 +- 3 files changed, 117 insertions(+), 4 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index 61496f2f5..1e1cd9ecc 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -18,6 +18,7 @@ """ import math +from copy import copy from . import ( Vector, Plane, @@ -71,6 +72,7 @@ def __init__(self, obj): self.objects = [] self.ctx = CQContext() self.parent = None + self._tag = None if obj: # guarded because sometimes None for internal use self.objects.append(obj) @@ -94,6 +96,17 @@ def newObject(self, objlist): r.objects = list(objlist) return r + def tag(self, name): + """ + Tags the current CQ object for later reference. + + :param name: the name to tag this object with + :type name: string + :returns: self, a cq object with tag applied + """ + self._tag = name + return self + def _collectProperty(self, propName): """ Collects all of the values for propName, @@ -266,6 +279,22 @@ def val(self): """ return self.objects[0] + def getTagged(self, name): + """ + Search the parent chain for a an object with tag == name. + + :param name: the tag to search for + :type name: string + :returns: the first CQ object in the parent chain with tag == name + :raises: ValueError if no object tagged name in the chain + """ + if self._tag == name: + return self + if self.parent is None: + raise ValueError("No CQ object named {} in chain".format(name)) + else: + return self.parent.getTagged(name) + def toOCC(self): """ Directly returns the wrapped FreeCAD object to cut down on the amount of boiler plate code @@ -430,6 +459,31 @@ def _computeXdir(normal): # a new workplane has the center of the workplane on the stack return s + def copyWorkplane(self, obj): + """ + Copies the workplane from obj. + + :param obj: an object to copy the workplane from + :type obj: a CQ object + :returns: a CQ object with obj's workplane + """ + out = Workplane(obj.plane) + out.parent = self + out.ctx = self.ctx + return out + + def copyWorkplaneFromTagged(self, name): + """ + Copies the workplane from a tagged parent. + + :param name: tag to search for + :type name: string + :returns: a CQ object with name's workplane + """ + tagged = self.getTagged(name) + out = self.copyWorkplane(tagged) + return out + def first(self): """ Return the first item on the stack @@ -1007,6 +1061,7 @@ def __init__(self, inPlane, origin=(0, 0, 0), obj=None): self.objects = [self.plane.origin] self.parent = None self.ctx = CQContext() + self._tag = None def transformed(self, rotate=(0, 0, 0), offset=(0, 0, 0)): """ @@ -1048,7 +1103,7 @@ def newObject(self, objlist): # copy the current state to the new object ns = Workplane("XY") - ns.plane = self.plane + ns.plane = copy(self.plane) ns.parent = self ns.objects = list(objlist) ns.ctx = self.ctx @@ -1215,8 +1270,9 @@ def center(self, x, y): The result is a cube with a round boss on the corner """ "Shift local coordinates to the specified location, according to current coordinates" - self.plane.setOrigin2d(x, y) - n = self.newObject([self.plane.origin]) + new_origin = self.plane.toWorldCoords((x, y)) + n = self.newObject([new_origin]) + n.plane.setOrigin2d(x, y) return n def lineTo(self, x, y, forConstruction=False): diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 46dfc4121..651be8e92 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -2742,3 +2742,60 @@ def test_assembleEdges(self): ) edge_wire = [o.vals()[0] for o in edge_wire.all()] edge_wire = Wire.assembleEdges(edge_wire) + + def testTag(self): + + # test tagging + Workplane("XY").pushPoints([(-2, 0), (2, 0)]).box(1, 1, 1, combine=False) + result = (Workplane("XY").pushPoints([(-2, 0), (2, 0)]) + .box(1, 1, 1, combine=False).tag("2 solids") + .union(Workplane("XY").box(6, 1, 1))) + self.assertEqual(len(result.objects), 1) + result = result.getTagged("2 solids") + self.assertEqual(len(result.objects), 2) + + # tags can be used to create geometry for construction + part = ( # create a base solid + Workplane("XY").box(1, 1, 1).tag("base") + # do some stuff "for construction" + .faces(">Y").workplane().box(1, 3, 1) + .faces(">Z").workplane().box(2, 1, 1) + .faces(">X").workplane() + # create an edge, which CadQuery adds to the modelling context + .circle(0.5) + # go back to base object, but modelling context is preserved + .getTagged("base") + .faces(">Z").workplane().circle(0.5) + # loft between the top of the base object and the wire at the end of the "for construction" code + .loft() + ) + # assert face is made at the end of the construction geometry + self.assertTupleAlmostEquals(part.faces(">X").val().Center().toTuple(), + (1.0, 0.5, 1.5), + 9) + # assert face points in the x-direction, like the construction geometry + self.assertTupleAlmostEquals(part.faces(">X").val().normalAt().toTuple(), + (1.0, 0, 0), + 9) + + def testCopyWorkplane(self): + + obj0 = Workplane("XY").box(1, 1, 10).faces(">Z").workplane() + obj1 = Workplane("XY").copyWorkplane(obj0).box(1, 1, 1) + self.assertTupleAlmostEquals((0, 0, 5), + obj1.val().Center().toTuple(), + 9) + + def testCopyWorkplaneFromTagged(self): + + # create a flat, wide base. Extrude one object 4 units high, another + # object ontop of it 6 units high. Go back to base plane. Extrude an + # object 11 units high. Assert that top face is 11 units high. + result = (Workplane("XY").box(10, 10, 1, centered=(True, True, False)) + .faces(">Z").workplane().tag("base").center(3, 0).rect(2, 2) + .extrude(4).faces(">Z").workplane().circle(1).extrude(6) + .copyWorkplaneFromTagged("base").center(-3, 0).circle(1) + .extrude(11)) + self.assertTupleAlmostEquals(result.faces(">Z").val().Center().toTuple(), + (-3, 0, 12), + 9) diff --git a/tests/test_selectors.py b/tests/test_selectors.py index 049a5a4b6..4bddb8bae 100644 --- a/tests/test_selectors.py +++ b/tests/test_selectors.py @@ -28,7 +28,7 @@ def testWorkplaneCenter(self): self.assertTupleAlmostEquals((0.0, 0.0, 0.0), s.plane.origin.toTuple(), 3) # move origin and confirm center moves - s.center(-2.0, -2.0) + s = s.center(-2.0, -2.0) # current point should be 0,0, but From b38f01711cafd3a734786b53685803180821194d Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Sun, 22 Dec 2019 08:22:26 +1030 Subject: [PATCH 02/11] Added the ability to select from a tagged object --- cadquery/cq.py | 51 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index 1e1cd9ecc..a11429b3e 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -576,20 +576,25 @@ def findFace(self, searchStack=True, searchParents=True): return self._findType(Face, searchStack, searchParents) - def _selectObjects(self, objType, selector=None): + def _selectObjects(self, objType, selector=None, tag=None): """ Filters objects of the selected type with the specified selector,and returns results :param objType: the type of object we are searching for :type objType: string: (Vertex|Edge|Wire|Solid|Shell|Compound|CompSolid) + :param tag: if set, search the tagged CQ object instead of self + :type tag: string :return: a CQ object with the selected objects on the stack. **Implementation Note**: This is the base implementation of the vertices,edges,faces, solids,shells, and other similar selector methods. It is a useful extension point for plugin developers to make other selector methods. """ + cq_obj = self + if tag: + cq_obj = self.getTagged(tag) # A single list of all faces from all objects on the stack - toReturn = self._collectProperty(objType) + toReturn = cq_obj._collectProperty(objType) if selector is not None: if isinstance(selector, str) or isinstance(selector, str): @@ -600,7 +605,7 @@ def _selectObjects(self, objType, selector=None): return self.newObject(toReturn) - def vertices(self, selector=None): + def vertices(self, selector=None, tag=None): """ Select the vertices of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the vertices of all objects are collected and a list of @@ -608,6 +613,8 @@ def vertices(self, selector=None): :param selector: :type selector: None, a Selector object, or a string selector expression. + :param tag: if set, search the tagged CQ object instead of self + :type tag: string :return: a CQ object who's stack contains the *distinct* vertices of *all* objects on the current stack, after being filtered by the selector, if provided @@ -629,9 +636,9 @@ def vertices(self, selector=None): :py:class:`StringSyntaxSelector` """ - return self._selectObjects("Vertices", selector) + return self._selectObjects("Vertices", selector, tag) - def faces(self, selector=None): + def faces(self, selector=None, tag=None): """ Select the faces of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the faces of all objects are collected and a list of all the @@ -639,6 +646,8 @@ def faces(self, selector=None): :param selector: A selector :type selector: None, a Selector object, or a string selector expression. + :param tag: if set, search the tagged CQ object instead of self + :type tag: string :return: a CQ object who's stack contains all of the *distinct* faces of *all* objects on the current stack, filtered by the provided selector. @@ -661,9 +670,9 @@ def faces(self, selector=None): See more about selectors HERE """ - return self._selectObjects("Faces", selector) + return self._selectObjects("Faces", selector, tag) - def edges(self, selector=None): + def edges(self, selector=None, tag=None): """ Select the edges of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the edges of all objects are collected and a list of all the @@ -671,6 +680,8 @@ def edges(self, selector=None): :param selector: A selector :type selector: None, a Selector object, or a string selector expression. + :param tag: if set, search the tagged CQ object instead of self + :type tag: string :return: a CQ object who's stack contains all of the *distinct* edges of *all* objects on the current stack, filtered by the provided selector. @@ -692,9 +703,9 @@ def edges(self, selector=None): See more about selectors HERE """ - return self._selectObjects("Edges", selector) + return self._selectObjects("Edges", selector, tag) - def wires(self, selector=None): + def wires(self, selector=None, tag=None): """ Select the wires of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the wires of all objects are collected and a list of all the @@ -702,6 +713,8 @@ def wires(self, selector=None): :param selector: A selector :type selector: None, a Selector object, or a string selector expression. + :param tag: if set, search the tagged CQ object instead of self + :type tag: string :return: a CQ object who's stack contains all of the *distinct* wires of *all* objects on the current stack, filtered by the provided selector. @@ -715,9 +728,9 @@ def wires(self, selector=None): See more about selectors HERE """ - return self._selectObjects("Wires", selector) + return self._selectObjects("Wires", selector, tag) - def solids(self, selector=None): + def solids(self, selector=None, tag=None): """ Select the solids of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the solids of all objects are collected and a list of all the @@ -725,6 +738,8 @@ def solids(self, selector=None): :param selector: A selector :type selector: None, a Selector object, or a string selector expression. + :param tag: if set, search the tagged CQ object instead of self + :type tag: string :return: a CQ object who's stack contains all of the *distinct* solids of *all* objects on the current stack, filtered by the provided selector. @@ -741,9 +756,9 @@ def solids(self, selector=None): See more about selectors HERE """ - return self._selectObjects("Solids", selector) + return self._selectObjects("Solids", selector, tag) - def shells(self, selector=None): + def shells(self, selector=None, tag=None): """ Select the shells of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the shells of all objects are collected and a list of all the @@ -751,6 +766,8 @@ def shells(self, selector=None): :param selector: A selector :type selector: None, a Selector object, or a string selector expression. + :param tag: if set, search the tagged CQ object instead of self + :type tag: string :return: a CQ object who's stack contains all of the *distinct* solids of *all* objects on the current stack, filtered by the provided selector. @@ -761,9 +778,9 @@ def shells(self, selector=None): See more about selectors HERE """ - return self._selectObjects("Shells", selector) + return self._selectObjects("Shells", selector, tag) - def compounds(self, selector=None): + def compounds(self, selector=None, tag=None): """ Select compounds on the stack, optionally filtering the selection. If there are multiple objects on the stack, they are collected and a list of all the distinct compounds @@ -771,6 +788,8 @@ def compounds(self, selector=None): :param selector: A selector :type selector: None, a Selector object, or a string selector expression. + :param tag: if set, search the tagged CQ object instead of self + :type tag: string :return: a CQ object who's stack contains all of the *distinct* solids of *all* objects on the current stack, filtered by the provided selector. @@ -779,7 +798,7 @@ def compounds(self, selector=None): See more about selectors HERE """ - return self._selectObjects("Compounds", selector) + return self._selectObjects("Compounds", selector, tag) def toSvg(self, opts=None): """ From 488a6144a59cc631a1fa8ccf2ab589477aa210dd Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Sun, 22 Dec 2019 13:54:59 +1030 Subject: [PATCH 03/11] Added tests for tags in selectors --- tests/test_cadquery.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 651be8e92..b4c2ba9e5 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -2799,3 +2799,32 @@ def testCopyWorkplaneFromTagged(self): self.assertTupleAlmostEquals(result.faces(">Z").val().Center().toTuple(), (-3, 0, 12), 9) + + def testTagSelectors(self): + + result0 = Workplane("XY").box(1, 1, 1).tag("box").sphere(1) + # result is currently a sphere + self.assertEqual(1, result0.faces().size()) + # a box has 8 vertices + self.assertEqual(8, result0.vertices(tag="box").size()) + # 6 faces + self.assertEqual(6, result0.faces(tag="box").size()) + # 12 edges + self.assertEqual(12, result0.edges(tag="box").size()) + # 6 wires + self.assertEqual(6, result0.wires(tag="box").size()) + + # create two solids, tag them, join to one solid + result1 = (Workplane("XY").pushPoints([(1, 0), (-1, 0)]).box(1, 1, 1) + .tag("boxes").sphere(1)) + self.assertEqual(1, result1.solids().size()) + self.assertEqual(2, result1.solids(tag="boxes").size()) + self.assertEqual(1, result1.shells().size()) + self.assertEqual(2, result1.shells(tag="boxes").size()) + + # create 4 individual objects, tag it, then combine to one compound + result2 = (Workplane("XY").rect(4, 4).vertices() + .box(1, 1, 1, combine=False).tag("4 objs")) + result2 = result2.newObject([Compound.makeCompound(result2.objects)]) + self.assertEqual(1, result2.compounds().size()) + self.assertEqual(0, result2.compounds(tag="4 objs").size()) From 6d98e29b151379a0878420f481a23e4e97128378 Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Tue, 24 Dec 2019 10:11:42 +1030 Subject: [PATCH 04/11] Simplify selector code --- cadquery/cq.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index a11429b3e..a5c3ea90f 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -590,9 +590,7 @@ def _selectObjects(self, objType, selector=None, tag=None): solids,shells, and other similar selector methods. It is a useful extension point for plugin developers to make other selector methods. """ - cq_obj = self - if tag: - cq_obj = self.getTagged(tag) + cq_obj = self.getTagged(tag) if tag else self # A single list of all faces from all objects on the stack toReturn = cq_obj._collectProperty(objType) From b54f03abf5775358cb0ed942175f2dde360299e4 Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Wed, 25 Dec 2019 21:34:05 +1030 Subject: [PATCH 05/11] Renamed copyWorkplaneFromTagged to workplaneFromTagged --- cadquery/cq.py | 2 +- tests/test_cadquery.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index a5c3ea90f..aa2225d3a 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -472,7 +472,7 @@ def copyWorkplane(self, obj): out.ctx = self.ctx return out - def copyWorkplaneFromTagged(self, name): + def workplaneFromTagged(self, name): """ Copies the workplane from a tagged parent. diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index b4c2ba9e5..0520fb41a 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -2786,7 +2786,7 @@ def testCopyWorkplane(self): obj1.val().Center().toTuple(), 9) - def testCopyWorkplaneFromTagged(self): + def testWorkplaneFromTagged(self): # create a flat, wide base. Extrude one object 4 units high, another # object ontop of it 6 units high. Go back to base plane. Extrude an @@ -2794,7 +2794,7 @@ def testCopyWorkplaneFromTagged(self): result = (Workplane("XY").box(10, 10, 1, centered=(True, True, False)) .faces(">Z").workplane().tag("base").center(3, 0).rect(2, 2) .extrude(4).faces(">Z").workplane().circle(1).extrude(6) - .copyWorkplaneFromTagged("base").center(-3, 0).circle(1) + .workplaneFromTagged("base").center(-3, 0).circle(1) .extrude(11)) self.assertTupleAlmostEquals(result.faces(">Z").val().Center().toTuple(), (-3, 0, 12), From 89e75470d0459025edca607d6b03f653c8b94afb Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Sat, 28 Dec 2019 20:46:10 +1030 Subject: [PATCH 06/11] Added examples to docs --- doc/examples.rst | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ doc/primer.rst | 2 ++ 2 files changed, 77 insertions(+) diff --git a/doc/examples.rst b/doc/examples.rst index 0f3662e0d..559f5b3b2 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -460,6 +460,32 @@ This example uses an offset workplane to make a compound object, which is perfec * :py:meth:`Workplane.box` * :py:meth:`Workplane` +Copying Workplanes +-------------------------- + +An existing CQ object can copy a workplane from another CQ object. + +.. cq_plot:: + + result = (cq.Workplane("front").circle(1).extrude(10) # make a cylinder + # We want to make a second cylinder perpendicular to the first, + # but we have no face to base the workplane off + .copyWorkplane( + # create a temporary object with the required workplane + cq.Workplane("right", origin=(-5, 0, 0)) + ).circle(1).extrude(10)) + show_object(result) + +.. topic:: API References + + .. hlist: + :columns: 2 + + * :py:meth:`CQ.copyWorkplane` **!** + * :py:meth:`Workplane.circle` + * :py:meth:`Workplane.extrude` + * :py:meth:`Workplane` + Rotated Workplanes -------------------------- @@ -604,6 +630,55 @@ Here we fillet all of the edges of a simple plate. * :py:meth:`Workplane.edges` * :py:meth:`Workplane` +Tagging objects +---------------- + +The :py:meth:`CQ.tag` method can be used to tag a particular object in the chain with a string. At a later point in the chain, the method :py:meth:`CQ.getTagged` can be used to return the previously tagged object (note that CQ objects share the modelling context throughout a chain, so going back to a previously tagged object will not change what is in the modelling context eg. unextruded edges). + +The method :py:meth:`CQ.getTagged` is awkward to use in chained calls (see :ref:`chaining`), the main utility of tags is when they are combined with other functions. The :py:meth:`CQ.workplaneFromTagged` method applies :py:meth:`CQ.copyWorkplane` to a tagged object. For example, when extruding two different solids from a surface, after the first solid is extruded it can become difficult to reselect the original surface with CadQuery's other selectors. + +.. cq_plot:: + + result = (cq.Workplane("XY") + # create and tag the base workplane + .box(10, 10, 10).faces(">Z").workplane().tag("baseplane") + # extrude a cylinder + .center(-3, 0).circle(1).extrude(3) + # to reselect the base workplane, simply + .workplaneFromTagged("baseplane") + # extrude a second cylinder + .center(3, 0).circle(1).extrude(2)) + show_object(result) + + +Tags can also be used with most selectors, including :py:meth:`CQ.vertices`, :py:meth:`CQ.faces`, :py:meth:`CQ.edges`, :py:meth:`CQ.wires`, :py:meth:`CQ.shells`, :py:meth:`CQ.solids` and :py:meth:`CQ.compounds`. + +.. cq_plot:: + + result = (cq.Workplane("XY") + # create a triangular prism and tag it + .polygon(3, 5).extrude(4).tag("prism") + # create a sphere that obscures the prism + .sphere(10) + # create features based on the prism's faces + .faces("X", tag="prism").faces(">Y").workplane().circle(1).cutThruAll()) + show_object(result) + +.. topic:: Api References + + .. hlist:: + :columns: 2 + + * :py:meth:`CQ.tag` **!** + * :py:meth:`CQ.getTagged` **!** + * :py:meth:`CQ.workplaneFromTagged` **!** + * :py:meth:`Workplane.extrude` + * :py:meth:`Workplane.cutThruAll` + * :py:meth:`Workplane.circle` + * :py:meth:`Workplane.faces` + * :py:meth:`Workplane` + A Parametric Bearing Pillow Block ------------------------------------ diff --git a/doc/primer.rst b/doc/primer.rst index f036de3f2..a8ee7e8b0 100644 --- a/doc/primer.rst +++ b/doc/primer.rst @@ -111,6 +111,8 @@ backwards in the stack to get the face as well:: You can browse stack access methods here: :ref:`stackMethods`. +.. _chaining: + Chaining --------------------------- From fa3277e6a0cdcbe85670a86b566433e82ff107be Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Sun, 19 Jan 2020 08:41:43 +1030 Subject: [PATCH 07/11] Renamed getTagged to _getTagged --- cadquery/cq.py | 8 ++++---- tests/test_cadquery.py | 26 +------------------------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/cadquery/cq.py b/cadquery/cq.py index aa2225d3a..818c77343 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -279,7 +279,7 @@ def val(self): """ return self.objects[0] - def getTagged(self, name): + def _getTagged(self, name): """ Search the parent chain for a an object with tag == name. @@ -293,7 +293,7 @@ def getTagged(self, name): if self.parent is None: raise ValueError("No CQ object named {} in chain".format(name)) else: - return self.parent.getTagged(name) + return self.parent._getTagged(name) def toOCC(self): """ @@ -480,7 +480,7 @@ def workplaneFromTagged(self, name): :type name: string :returns: a CQ object with name's workplane """ - tagged = self.getTagged(name) + tagged = self._getTagged(name) out = self.copyWorkplane(tagged) return out @@ -590,7 +590,7 @@ def _selectObjects(self, objType, selector=None, tag=None): solids,shells, and other similar selector methods. It is a useful extension point for plugin developers to make other selector methods. """ - cq_obj = self.getTagged(tag) if tag else self + cq_obj = self._getTagged(tag) if tag else self # A single list of all faces from all objects on the stack toReturn = cq_obj._collectProperty(objType) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 0520fb41a..64e8e36ae 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -2751,33 +2751,9 @@ def testTag(self): .box(1, 1, 1, combine=False).tag("2 solids") .union(Workplane("XY").box(6, 1, 1))) self.assertEqual(len(result.objects), 1) - result = result.getTagged("2 solids") + result = result._getTagged("2 solids") self.assertEqual(len(result.objects), 2) - # tags can be used to create geometry for construction - part = ( # create a base solid - Workplane("XY").box(1, 1, 1).tag("base") - # do some stuff "for construction" - .faces(">Y").workplane().box(1, 3, 1) - .faces(">Z").workplane().box(2, 1, 1) - .faces(">X").workplane() - # create an edge, which CadQuery adds to the modelling context - .circle(0.5) - # go back to base object, but modelling context is preserved - .getTagged("base") - .faces(">Z").workplane().circle(0.5) - # loft between the top of the base object and the wire at the end of the "for construction" code - .loft() - ) - # assert face is made at the end of the construction geometry - self.assertTupleAlmostEquals(part.faces(">X").val().Center().toTuple(), - (1.0, 0.5, 1.5), - 9) - # assert face points in the x-direction, like the construction geometry - self.assertTupleAlmostEquals(part.faces(">X").val().normalAt().toTuple(), - (1.0, 0, 0), - 9) - def testCopyWorkplane(self): obj0 = Workplane("XY").box(1, 1, 10).faces(">Z").workplane() From 911c08f453e2edf666655d29d7224fb3f1b3d2f1 Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Thu, 23 Jan 2020 20:26:17 +1030 Subject: [PATCH 08/11] removed getTagged from docs --- doc/examples.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/examples.rst b/doc/examples.rst index 559f5b3b2..a25975e4d 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -633,9 +633,9 @@ Here we fillet all of the edges of a simple plate. Tagging objects ---------------- -The :py:meth:`CQ.tag` method can be used to tag a particular object in the chain with a string. At a later point in the chain, the method :py:meth:`CQ.getTagged` can be used to return the previously tagged object (note that CQ objects share the modelling context throughout a chain, so going back to a previously tagged object will not change what is in the modelling context eg. unextruded edges). +The :py:meth:`CQ.tag` method can be used to tag a particular object in the chain with a string, so that it can be refered to later in the chain. -The method :py:meth:`CQ.getTagged` is awkward to use in chained calls (see :ref:`chaining`), the main utility of tags is when they are combined with other functions. The :py:meth:`CQ.workplaneFromTagged` method applies :py:meth:`CQ.copyWorkplane` to a tagged object. For example, when extruding two different solids from a surface, after the first solid is extruded it can become difficult to reselect the original surface with CadQuery's other selectors. +The :py:meth:`CQ.workplaneFromTagged` method applies :py:meth:`CQ.copyWorkplane` to a tagged object. For example, when extruding two different solids from a surface, after the first solid is extruded it can become difficult to reselect the original surface with CadQuery's other selectors. .. cq_plot:: @@ -664,7 +664,7 @@ Tags can also be used with most selectors, including :py:meth:`CQ.vertices`, :py .faces("X", tag="prism").faces(">Y").workplane().circle(1).cutThruAll()) show_object(result) - + .. topic:: Api References .. hlist:: From 04c8b80084dcd0c21fd1fbedd3499a42363ed79e Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Thu, 23 Jan 2020 20:43:34 +1030 Subject: [PATCH 09/11] Added tags to primer/concepts docs --- doc/primer.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/primer.rst b/doc/primer.rst index a8ee7e8b0..88d2c7f5a 100644 --- a/doc/primer.rst +++ b/doc/primer.rst @@ -119,6 +119,11 @@ Chaining All CadQuery methods return another CadQuery object, so that you can chain the methods together fluently. Use the core CQ methods to get at the objects that were created. +Each time a new CadQuery object is produced during these chained calls, it has a ``parent`` attribute that points +to the CadQuery object that created it. Several CadQuery methods search this parent chain, for example when searching +for the context solid. You can also give a CadQuery object a tag, and further down your chain of CadQuery calls you +can refer back to this particular object using it's tag. + The Context Solid --------------------------- From 95fb436081312b670b5ec618f51d1db23f722e98 Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Thu, 23 Jan 2020 21:25:32 +1030 Subject: [PATCH 10/11] test_cadquery formatting --- tests/test_cadquery.py | 62 +++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 64e8e36ae..9f8851565 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -2746,10 +2746,13 @@ def test_assembleEdges(self): def testTag(self): # test tagging - Workplane("XY").pushPoints([(-2, 0), (2, 0)]).box(1, 1, 1, combine=False) - result = (Workplane("XY").pushPoints([(-2, 0), (2, 0)]) - .box(1, 1, 1, combine=False).tag("2 solids") - .union(Workplane("XY").box(6, 1, 1))) + result = ( + Workplane("XY") + .pushPoints([(-2, 0), (2, 0)]) + .box(1, 1, 1, combine=False) + .tag("2 solids") + .union(Workplane("XY") .box(6, 1, 1)) + ) self.assertEqual(len(result.objects), 1) result = result._getTagged("2 solids") self.assertEqual(len(result.objects), 2) @@ -2758,23 +2761,34 @@ def testCopyWorkplane(self): obj0 = Workplane("XY").box(1, 1, 10).faces(">Z").workplane() obj1 = Workplane("XY").copyWorkplane(obj0).box(1, 1, 1) - self.assertTupleAlmostEquals((0, 0, 5), - obj1.val().Center().toTuple(), - 9) + self.assertTupleAlmostEquals((0, 0, 5), obj1.val().Center().toTuple(), 9) def testWorkplaneFromTagged(self): # create a flat, wide base. Extrude one object 4 units high, another # object ontop of it 6 units high. Go back to base plane. Extrude an # object 11 units high. Assert that top face is 11 units high. - result = (Workplane("XY").box(10, 10, 1, centered=(True, True, False)) - .faces(">Z").workplane().tag("base").center(3, 0).rect(2, 2) - .extrude(4).faces(">Z").workplane().circle(1).extrude(6) - .workplaneFromTagged("base").center(-3, 0).circle(1) - .extrude(11)) - self.assertTupleAlmostEquals(result.faces(">Z").val().Center().toTuple(), - (-3, 0, 12), - 9) + result = ( + Workplane("XY") + .box(10, 10, 1, centered=(True, True, False)) + .faces(">Z") + .workplane() + .tag("base") + .center(3, 0) + .rect(2, 2) + .extrude(4) + .faces(">Z") + .workplane() + .circle(1) + .extrude(6) + .workplaneFromTagged("base") + .center(-3, 0) + .circle(1) + .extrude(11) + ) + self.assertTupleAlmostEquals( + result.faces(">Z").val().Center().toTuple(), (-3, 0, 12), 9 + ) def testTagSelectors(self): @@ -2791,16 +2805,26 @@ def testTagSelectors(self): self.assertEqual(6, result0.wires(tag="box").size()) # create two solids, tag them, join to one solid - result1 = (Workplane("XY").pushPoints([(1, 0), (-1, 0)]).box(1, 1, 1) - .tag("boxes").sphere(1)) + result1 = ( + Workplane("XY") + .pushPoints([(1, 0), (-1, 0)]) + .box(1, 1, 1) + .tag("boxes") + .sphere(1) + ) self.assertEqual(1, result1.solids().size()) self.assertEqual(2, result1.solids(tag="boxes").size()) self.assertEqual(1, result1.shells().size()) self.assertEqual(2, result1.shells(tag="boxes").size()) # create 4 individual objects, tag it, then combine to one compound - result2 = (Workplane("XY").rect(4, 4).vertices() - .box(1, 1, 1, combine=False).tag("4 objs")) + result2 = ( + Workplane("XY") + .rect(4, 4) + .vertices() + .box(1, 1, 1, combine=False) + .tag("4 objs") + ) result2 = result2.newObject([Compound.makeCompound(result2.objects)]) self.assertEqual(1, result2.compounds().size()) self.assertEqual(0, result2.compounds(tag="4 objs").size()) From 09da93a01422fd34046206d3c3ec59722daeddc9 Mon Sep 17 00:00:00 2001 From: Marcus Boyd Date: Thu, 23 Jan 2020 22:22:53 +1030 Subject: [PATCH 11/11] formatting --- tests/test_cadquery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 9f8851565..2057e58fa 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -2751,7 +2751,7 @@ def testTag(self): .pushPoints([(-2, 0), (2, 0)]) .box(1, 1, 1, combine=False) .tag("2 solids") - .union(Workplane("XY") .box(6, 1, 1)) + .union(Workplane("XY").box(6, 1, 1)) ) self.assertEqual(len(result.objects), 1) result = result._getTagged("2 solids")