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
111 changes: 92 additions & 19 deletions cadquery/cq.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"""

import math
from copy import copy
from . import (
Vector,
Plane,
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 workplaneFromTagged(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
Expand Down Expand Up @@ -522,20 +576,23 @@ 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._getTagged(tag) if tag else self
# 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):
Expand All @@ -546,14 +603,16 @@ 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
all the distinct vertices is returned.

: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

Expand All @@ -575,16 +634,18 @@ 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
distinct faces is returned.

: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.

Expand All @@ -607,16 +668,18 @@ 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
distinct edges is returned.

: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.

Expand All @@ -638,16 +701,18 @@ 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
distinct wires is returned.

: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.

Expand All @@ -661,16 +726,18 @@ 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
distinct solids is returned.

: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.

Expand All @@ -687,16 +754,18 @@ 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
distinct shells is returned.

: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.

Expand All @@ -707,16 +776,18 @@ 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
is returned.

: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.

Expand All @@ -725,7 +796,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):
"""
Expand Down Expand Up @@ -1007,6 +1078,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)):
"""
Expand Down Expand Up @@ -1048,7 +1120,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
Expand Down Expand Up @@ -1215,8 +1287,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):
Expand Down
75 changes: 75 additions & 0 deletions doc/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------------

Expand Down Expand Up @@ -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, so that it can be refered to later in the chain.

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").workplane().circle(1).cutThruAll()
.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
------------------------------------

Expand Down
Loading