diff --git a/CadQuery/InitGui.py b/CadQuery/InitGui.py
index 769ae63..78e9dcf 100644
--- a/CadQuery/InitGui.py
+++ b/CadQuery/InitGui.py
@@ -51,6 +51,10 @@ class CadQueryWorkbench (Workbench):
libs_dir_path = os.path.join(module_base_path, 'Libs')
sys.path.insert(0, libs_dir_path)
+ # Tack on our CadQuery library git subtree
+ cq_lib_path = os.path.join(libs_dir_path, 'cadquery-lib')
+ sys.path.insert(1, cq_lib_path)
+
#Make sure we get the right libs under the FreeCAD installation
fc_base_path = os.path.dirname(os.path.dirname(module_base_path))
fc_lib_path = os.path.join(fc_base_path, 'lib')
diff --git a/CadQuery/Libs/cadquery/CQ.py b/CadQuery/Libs/cadquery/CQ.py
deleted file mode 100644
index f6c043a..0000000
--- a/CadQuery/Libs/cadquery/CQ.py
+++ /dev/null
@@ -1,2458 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
-"""
-
-import time
-import math
-from cadquery import *
-from cadquery import selectors
-from cadquery import exporters
-
-
-class CQContext(object):
- """
- A shared context for modeling.
-
- All objects in the same CQ chain share a reference to this same object instance
- which allows for shared state when needed,
- """
- def __init__(self):
- self.pendingWires = [] # a list of wires that have been created and need to be extruded
- self.pendingEdges = [] # a list of created pending edges that need to be joined into wires
- # a reference to the first point for a set of edges.
- # Used to determine how to behave when close() is called
- self.firstPoint = None
- self.tolerance = 0.0001 # user specified tolerance
-
-
-class CQ(object):
- """
- Provides enhanced functionality for a wrapped CAD primitive.
-
- Examples include feature selection, feature creation, 2d drawing
- using work planes, and 3d operations like fillets, shells, and splitting
- """
-
- def __init__(self, obj):
- """
- Construct a new CadQuery (CQ) object that wraps a CAD primitive.
-
- :param obj: Object to Wrap.
- :type obj: A CAD Primitive ( wire,vertex,face,solid,edge )
- """
- self.objects = []
- self.ctx = CQContext()
- self.parent = None
-
- if obj: # guarded because sometimes None for internal use
- self.objects.append(obj)
-
- def newObject(self, objlist):
- """
- Make a new CQ object.
-
- :param objlist: The stack of objects to use
- :type objlist: a list of CAD primitives ( wire,face,edge,solid,vertex,etc )
-
- The parent of the new object will be set to the current object,
- to preserve the chain correctly.
-
- Custom plugins and subclasses should use this method to create new CQ objects
- correctly.
- """
- r = CQ(None) # create a completely blank one
- r.parent = self
- r.ctx = self.ctx # context solid remains the same
- r.objects = list(objlist)
- return r
-
- def _collectProperty(self, propName):
- """
- Collects all of the values for propName,
- for all items on the stack.
- FreeCAD objects do not implement id correctly,
- so hashCode is used to ensure we don't add the same
- object multiple times.
-
- One weird use case is that the stack could have a solid reference object
- on it. This is meant to be a reference to the most recently modified version
- of the context solid, whatever it is.
- """
- all = {}
- for o in self.objects:
-
- # tricky-- if an object is a compound of solids,
- # do not return all of the solids underneath-- typically
- # then we'll keep joining to ourself
- if propName == 'Solids' and isinstance(o, Solid) and o.ShapeType() == 'Compound':
- for i in getattr(o, 'Compounds')():
- all[i.hashCode()] = i
- else:
- if hasattr(o, propName):
- for i in getattr(o, propName)():
- all[i.hashCode()] = i
-
- return list(all.values())
-
- def split(self, keepTop=False, keepBottom=False):
- """
- Splits a solid on the stack into two parts, optionally keeping the separate parts.
-
- :param boolean keepTop: True to keep the top, False or None to discard it
- :param boolean keepBottom: True to keep the bottom, False or None to discard it
- :raises: ValueError if keepTop and keepBottom are both false.
- :raises: ValueError if there is not a solid in the current stack or the parent chain
- :returns: CQ object with the desired objects on the stack.
-
- The most common operation splits a solid and keeps one half. This sample creates
- split bushing::
-
- #drill a hole in the side
- c = Workplane().box(1,1,1).faces(">Z").workplane().circle(0.25).cutThruAll()F
- #now cut it in half sideways
- c.faces(">Y").workplane(-0.5).split(keepTop=True)
- """
-
- solid = self.findSolid()
-
- if (not keepTop) and (not keepBottom):
- raise ValueError("You have to keep at least one half")
-
- maxDim = solid.BoundingBox().DiagonalLength * 10.0
- topCutBox = self.rect(maxDim, maxDim)._extrude(maxDim)
- bottomCutBox = self.rect(maxDim, maxDim)._extrude(-maxDim)
-
- top = solid.cut(bottomCutBox)
- bottom = solid.cut(topCutBox)
-
- if keepTop and keepBottom:
- # Put both on the stack, leave original unchanged.
- return self.newObject([top, bottom])
- else:
- # Put the one we are keeping on the stack, and also update the
- # context solidto the one we kept.
- if keepTop:
- solid.wrapped = top.wrapped
- return self.newObject([top])
- else:
- solid.wrapped = bottom.wrapped
- return self.newObject([bottom])
-
- def combineSolids(self, otherCQToCombine=None):
- """
- !!!DEPRECATED!!! use union()
- Combines all solids on the current stack, and any context object, together
- into a single object.
-
- After the operation, the returned solid is also the context solid.
-
- :param otherCQToCombine: another CadQuery to combine.
- :return: a cQ object with the resulting combined solid on the stack.
-
- Most of the time, both objects will contain a single solid, which is
- combined and returned on the stack of the new object.
- """
- #loop through current stack objects, and combine them
- #TODO: combine other types of objects as well, like edges and wires
- toCombine = self.solids().vals()
-
- if otherCQToCombine:
- for obj in otherCQToCombine.solids().vals():
- toCombine.append(obj)
-
- if len(toCombine) < 1:
- raise ValueError("Cannot Combine: at least one solid required!")
-
- #get context solid and we don't want to find our own objects
- ctxSolid = self.findSolid(searchStack=False, searchParents=True)
-
- if ctxSolid is None:
- ctxSolid = toCombine.pop(0)
-
- #now combine them all. make sure to save a reference to the ctxSolid pointer!
- s = ctxSolid
- for tc in toCombine:
- s = s.fuse(tc)
-
- ctxSolid.wrapped = s.wrapped
- return self.newObject([s])
-
- def all(self):
- """
- Return a list of all CQ objects on the stack.
-
- useful when you need to operate on the elements
- individually.
-
- Contrast with vals, which returns the underlying
- objects for all of the items on the stack
- """
- return [self.newObject([o]) for o in self.objects]
-
- def size(self):
- """
- Return the number of objects currently on the stack
- """
- return len(self.objects)
-
- def vals(self):
- """
- get the values in the current list
-
- :rtype: list of FreeCAD objects
- :returns: the values of the objects on the stack.
-
- Contrast with :py:meth:`all`, which returns CQ objects for all of the items on the stack
- """
- return self.objects
-
- def add(self, obj):
- """
- Adds an object or a list of objects to the stack
-
- :param obj: an object to add
- :type obj: a CQ object, CAD primitive, or list of CAD primitives
- :return: a CQ object with the requested operation performed
-
- If an CQ object, the values of that object's stack are added. If a list of cad primitives,
- they are all added. If a single CAD primitive it is added
-
- Used in rare cases when you need to combine the results of several CQ results
- into a single CQ object. Shelling is one common example
- """
- if type(obj) == list:
- self.objects.extend(obj)
- elif type(obj) == CQ or type(obj) == Workplane:
- self.objects.extend(obj.objects)
- else:
- self.objects.append(obj)
- return self
-
- def val(self):
- """
- Return the first value on the stack
-
- :return: the first value on the stack.
- :rtype: A FreeCAD object or a SolidReference
- """
- return self.objects[0]
-
- def toFreecad(self):
- """
- Directly returns the wrapped FreeCAD object to cut down on the amount of boiler plate code
- needed when rendering a model in FreeCAD's 3D view.
- :return: The wrapped FreeCAD object
- :rtype A FreeCAD object or a SolidReference
- """
-
- return self.objects[0].wrapped
-
- def workplane(self, offset=0.0, invert=False):
- """
- Creates a new 2-D workplane, located relative to the first face on the stack.
-
- :param offset: offset for the work plane in the Z direction. Default
- :param invert: invert the Z direction from that of the face.
- :type offset: float or None=0.0
- :type invert: boolean or None=False
- :rtype: Workplane object ( which is a subclass of CQ )
-
- The first element on the stack must be a face, a set of
- co-planar faces or a vertex. If a vertex, then the parent
- item on the chain immediately before the vertex must be a
- face.
-
- The result will be a 2-d working plane
- with a new coordinate system set up as follows:
-
- * The origin will be located in the *center* of the
- face/faces, if a face/faces was selected. If a vertex was
- selected, the origin will be at the vertex, and located
- on the face.
- * The Z direction will be normal to the plane of the face,computed
- at the center point.
- * The X direction will be parallel to the x-y plane. If the workplane is parallel to
- the global x-y plane, the x direction of the workplane will co-incide with the
- global x direction.
-
- Most commonly, the selected face will be planar, and the workplane lies in the same plane
- of the face ( IE, offset=0). Occasionally, it is useful to define a face offset from
- an existing surface, and even more rarely to define a workplane based on a face that is
- not planar.
-
- To create a workplane without first having a face, use the Workplane() method.
-
- Future Enhancements:
- * Allow creating workplane from planar wires
- * Allow creating workplane based on an arbitrary point on a face, not just the center.
- For now you can work around by creating a workplane and then offsetting the center
- afterwards.
- """
- def _isCoPlanar(f0, f1):
- """Test if two faces are on the same plane."""
- p0 = f0.Center()
- p1 = f1.Center()
- n0 = f0.normalAt()
- n1 = f1.normalAt()
-
- # test normals (direction of planes)
- if not ((abs(n0.x-n1.x) < self.ctx.tolerance) or
- (abs(n0.y-n1.y) < self.ctx.tolerance) or
- (abs(n0.z-n1.z) < self.ctx.tolerance)):
- return False
-
- # test if p1 is on the plane of f0 (offset of planes)
- return abs(n0.dot(p0.sub(p1)) < self.ctx.tolerance)
-
- def _computeXdir(normal):
- """
- Figures out the X direction based on the given normal.
- :param :normal The direction that's normal to the plane.
- :type :normal A Vector
- :return A vector representing the X direction.
- """
- xd = Vector(0, 0, 1).cross(normal)
- if xd.Length < self.ctx.tolerance:
- #this face is parallel with the x-y plane, so choose x to be in global coordinates
- xd = Vector(1, 0, 0)
- return xd
-
- if len(self.objects) > 1:
- # are all objects 'PLANE'?
- if not all(o.geomType() == 'PLANE' for o in self.objects):
- raise ValueError("If multiple objects selected, they all must be planar faces.")
-
- # are all faces co-planar with each other?
- if not all(_isCoPlanar(self.objects[0], f) for f in self.objects[1:]):
- raise ValueError("Selected faces must be co-planar.")
-
- center = Shape.CombinedCenter(self.objects)
- normal = self.objects[0].normalAt()
- xDir = _computeXdir(normal)
-
- else:
- obj = self.objects[0]
-
- if isinstance(obj, Face):
- center = obj.Center()
- normal = obj.normalAt(center)
- xDir = _computeXdir(normal)
- else:
- if hasattr(obj, 'Center'):
- center = obj.Center()
- normal = self.plane.zDir
- xDir = self.plane.xDir
- else:
- raise ValueError("Needs a face or a vertex or point on a work plane")
-
- #invert if requested
- if invert:
- normal = normal.multiply(-1.0)
-
- #offset origin if desired
- offsetVector = normal.normalize().multiply(offset)
- offsetCenter = center.add(offsetVector)
-
- #make the new workplane
- plane = Plane(offsetCenter, xDir, normal)
- s = Workplane(plane)
- s.parent = self
- s.ctx = self.ctx
-
- #a new workplane has the center of the workplane on the stack
- return s
-
- def first(self):
- """
- Return the first item on the stack
- :returns: the first item on the stack.
- :rtype: a CQ object
- """
- return self.newObject(self.objects[0:1])
-
- def item(self, i):
- """
-
- Return the ith item on the stack.
- :rtype: a CQ object
- """
- return self.newObject([self.objects[i]])
-
- def last(self):
- """
- Return the last item on the stack.
- :rtype: a CQ object
- """
- return self.newObject([self.objects[-1]])
-
- def end(self):
- """
- Return the parent of this CQ element
- :rtype: a CQ object
- :raises: ValueError if there are no more parents in the chain.
-
- For example::
-
- CQ(obj).faces("+Z").vertices().end()
-
- will return the same as::
-
- CQ(obj).faces("+Z")
- """
- if self.parent:
- return self.parent
- else:
- raise ValueError("Cannot End the chain-- no parents!")
-
- def findSolid(self, searchStack=True, searchParents=True):
- """
- Finds the first solid object in the chain, searching from the current node
- backwards through parents until one is found.
-
- :param searchStack: should objects on the stack be searched first.
- :param searchParents: should parents be searched?
- :raises: ValueError if no solid is found in the current object or its parents,
- and errorOnEmpty is True
-
- This function is very important for chains that are modifying a single parent object,
- most often a solid.
-
- Most of the time, a chain defines or selects a solid, and then modifies it using workplanes
- or other operations.
-
- Plugin Developers should make use of this method to find the solid that should be modified,
- if the plugin implements a unary operation, or if the operation will automatically merge its
- results with an object already on the stack.
- """
- #notfound = ValueError("Cannot find a Valid Solid to Operate on!")
-
- if searchStack:
- for s in self.objects:
- if isinstance(s, Solid):
- return s
- elif isinstance(s, Compound):
- return s.Solids()
-
- if searchParents and self.parent is not None:
- return self.parent.findSolid(searchStack=True, searchParents=searchParents)
-
- return None
-
- def _selectObjects(self, objType, selector=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)
- :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.
- """
- # A single list of all faces from all objects on the stack
- toReturn = self._collectProperty(objType)
-
- if selector is not None:
- if isinstance(selector, str) or isinstance(selector, unicode):
- selectorObj = selectors.StringSyntaxSelector(selector)
- else:
- selectorObj = selector
- toReturn = selectorObj.filter(toReturn)
-
- return self.newObject(toReturn)
-
- def vertices(self, selector=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.
- :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
-
- If there are no vertices for any objects on the current stack, an empty CQ object
- is returned
-
- The typical use is to select the vertices of a single object on the stack. For example::
-
- Workplane().box(1,1,1).faces("+Z").vertices().size()
-
- returns 4, because the topmost face of cube will contain four vertices. While this::
-
- Workplane().box(1,1,1).faces().vertices().size()
-
- returns 8, because a cube has a total of 8 vertices
-
- **Note** Circles are peculiar, they have a single vertex at the center!
-
- :py:class:`StringSyntaxSelector`
-
- """
- return self._selectObjects('Vertices', selector)
-
- def faces(self, selector=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.
- :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.
-
- If there are no vertices for any objects on the current stack, an empty CQ object
- is returned.
-
- The typical use is to select the faces of a single object on the stack. For example::
-
- CQ(aCube).faces("+Z").size()
-
- returns 1, because a cube has one face with a normal in the +Z direction. Similarly::
-
- CQ(aCube).faces().size()
-
- returns 6, because a cube has a total of 6 faces, And::
-
- CQ(aCube).faces("|Z").size()
-
- returns 2, because a cube has 2 faces having normals parallel to the z direction
-
- See more about selectors HERE
- """
- return self._selectObjects('Faces', selector)
-
- def edges(self, selector=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.
- :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.
-
- If there are no edges for any objects on the current stack, an empty CQ object is returned
-
- The typical use is to select the edges of a single object on the stack. For example::
-
- CQ(aCube).faces("+Z").edges().size()
-
- returns 4, because a cube has one face with a normal in the +Z direction. Similarly::
-
- CQ(aCube).edges().size()
-
- returns 12, because a cube has a total of 12 edges, And::
-
- CQ(aCube).edges("|Z").size()
-
- returns 4, because a cube has 4 edges parallel to the z direction
-
- See more about selectors HERE
- """
- return self._selectObjects('Edges', selector)
-
- def wires(self, selector=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.
- :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.
-
- If there are no wires for any objects on the current stack, an empty CQ object is returned
-
- The typical use is to select the wires of a single object on the stack. For example::
-
- CQ(aCube).faces("+Z").wires().size()
-
- returns 1, because a face typically only has one outer wire
-
- See more about selectors HERE
- """
- return self._selectObjects('Wires', selector)
-
- def solids(self, selector=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.
- :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.
-
- If there are no solids for any objects on the current stack, an empty CQ object is returned
-
- The typical use is to select the a single object on the stack. For example::
-
- CQ(aCube).solids().size()
-
- returns 1, because a cube consists of one solid.
-
- It is possible for single CQ object ( or even a single CAD primitive ) to contain
- multiple solids.
-
- See more about selectors HERE
- """
- return self._selectObjects('Solids', selector)
-
- def shells(self, selector=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.
- :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.
-
- If there are no shells for any objects on the current stack, an empty CQ object is returned
-
- Most solids will have a single shell, which represents the outer surface. A shell will
- typically be composed of multiple faces.
-
- See more about selectors HERE
- """
- return self._selectObjects('Shells', selector)
-
- def compounds(self, selector=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.
- :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.
-
- A compound contains multiple CAD primitives that resulted from a single operation, such as
- a union, cut, split, or fillet. Compounds can contain multiple edges, wires, or solids.
-
- See more about selectors HERE
- """
- return self._selectObjects('Compounds', selector)
-
- def toSvg(self, opts=None):
- """
- Returns svg text that represents the first item on the stack.
-
- for testing purposes.
-
- :param opts: svg formatting options
- :type opts: dictionary, width and height
- :return: a string that contains SVG that represents this item.
- """
- return exporters.getSVG(self.val().wrapped, opts)
-
- def exportSvg(self, fileName):
- """
- Exports the first item on the stack as an SVG file
-
- For testing purposes mainly.
-
- :param fileName: the filename to export
- :type fileName: String, absolute path to the file
- """
- exporters.exportSVG(self, fileName)
-
- def rotateAboutCenter(self, axisEndPoint, angleDegrees):
- """
- Rotates all items on the stack by the specified angle, about the specified axis
-
- The center of rotation is a vector starting at the center of the object on the stack,
- and ended at the specified point.
-
- :param axisEndPoint: the second point of axis of rotation
- :type axisEndPoint: a three-tuple in global coordinates
- :param angleDegrees: the rotation angle, in degrees
- :type angleDegrees: float
- :returns: a CQ object, with all items rotated.
-
- WARNING: This version returns the same cq object instead of a new one-- the
- old object is not accessible.
-
- Future Enhancements:
- * A version of this method that returns a transformed copy, rather than modifying
- the originals
- * This method doesnt expose a very good interface, because the axis of rotation
- could be inconsistent between multiple objects. This is because the beginning
- of the axis is variable, while the end is fixed. This is fine when operating on
- one object, but is not cool for multiple.
- """
-
- #center point is the first point in the vector
- endVec = Vector(axisEndPoint)
-
- def _rot(obj):
- startPt = obj.Center()
- endPt = startPt + endVec
- return obj.rotate(startPt, endPt, angleDegrees)
-
- return self.each(_rot, False)
-
- def rotate(self, axisStartPoint, axisEndPoint, angleDegrees):
- """
- Returns a copy of all of the items on the stack rotated through and angle around the axis
- of rotation.
-
- :param axisStartPoint: The first point of the axis of rotation
- :type axisStartPoint: a 3-tuple of floats
- :type axisEndPoint: The second point of the axis of rotation
- :type axisEndPoint: a 3-tuple of floats
- :param angleDegrees: the rotation angle, in degrees
- :type angleDegrees: float
- :returns: a CQ object
- """
- return self.newObject([o.rotate(axisStartPoint, axisEndPoint, angleDegrees)
- for o in self.objects])
-
- def translate(self, vec):
- """
- Returns a copy of all of the items on the stack moved by the specified translation vector.
-
- :param tupleDistance: distance to move, in global coordinates
- :type tupleDistance: a 3-tuple of float
- :returns: a CQ object
- """
- return self.newObject([o.translate(vec) for o in self.objects])
-
-
- def shell(self, thickness):
- """
- Remove the selected faces to create a shell of the specified thickness.
-
- To shell, first create a solid, and *in the same chain* select the faces you wish to remove.
-
- :param thickness: a positive float, representing the thickness of the desired shell.
- Negative values shell inwards, positive values shell outwards.
- :raises: ValueError if the current stack contains objects that are not faces of a solid
- further up in the chain.
- :returns: a CQ object with the resulting shelled solid selected.
-
- This example will create a hollowed out unit cube, where the top most face is open,
- and all other walls are 0.2 units thick::
-
- Workplane().box(1,1,1).faces("+Z").shell(0.2)
-
- Shelling is one of the cases where you may need to use the add method to select several
- faces. For example, this example creates a 3-walled corner, by removing three faces
- of a cube::
-
- s = Workplane().box(1,1,1)
- s1 = s.faces("+Z")
- s1.add(s.faces("+Y")).add(s.faces("+X"))
- self.saveModel(s1.shell(0.2))
-
- This fairly yucky syntax for selecting multiple faces is planned for improvement
-
- **Note**: When sharp edges are shelled inwards, they remain sharp corners, but **outward**
- shells are automatically filleted, because an outward offset from a corner generates
- a radius.
-
-
- Future Enhancements:
- Better selectors to make it easier to select multiple faces
- """
- solidRef = self.findSolid()
-
- for f in self.objects:
- if type(f) != Face:
- raise ValueError("Shelling requires that faces be selected")
-
- s = solidRef.shell(self.objects, thickness)
- solidRef.wrapped = s.wrapped
- return self.newObject([s])
-
- def fillet(self, radius):
- """
- Fillets a solid on the selected edges.
-
- The edges on the stack are filleted. The solid to which the edges belong must be in the
- parent chain of the selected edges.
-
- :param radius: the radius of the fillet, must be > zero
- :type radius: positive float
- :raises: ValueError if at least one edge is not selected
- :raises: ValueError if the solid containing the edge is not in the chain
- :returns: cq object with the resulting solid selected.
-
- This example will create a unit cube, with the top edges filleted::
-
- s = Workplane().box(1,1,1).faces("+Z").edges().fillet(0.1)
- """
- # TODO: we will need much better edge selectors for this to work
- # TODO: ensure that edges selected actually belong to the solid in the chain, otherwise,
- # TODO: we segfault
-
- solid = self.findSolid()
-
- edgeList = self.edges().vals()
- if len(edgeList) < 1:
- raise ValueError("Fillets requires that edges be selected")
-
- s = solid.fillet(radius, edgeList)
- solid.wrapped = s.wrapped
- return self.newObject([s])
-
- def chamfer(self, length, length2=None):
- """
- Chamfers a solid on the selected edges.
-
- The edges on the stack are chamfered. The solid to which the
- edges belong must be in the parent chain of the selected
- edges.
-
- Optional parameter `length2` can be supplied with a different
- value than `length` for a chamfer that is shorter on one side
- longer on the other side.
-
- :param length: the length of the fillet, must be greater than zero
- :param length2: optional parameter for asymmetrical chamfer
- :type length: positive float
- :type length2: positive float
- :raises: ValueError if at least one edge is not selected
- :raises: ValueError if the solid containing the edge is not in the chain
- :returns: cq object with the resulting solid selected.
-
- This example will create a unit cube, with the top edges chamfered::
-
- s = Workplane("XY").box(1,1,1).faces("+Z").chamfer(0.1)
-
- This example will create chamfers longer on the sides::
-
- s = Workplane("XY").box(1,1,1).faces("+Z").chamfer(0.2, 0.1)
- """
- solid = self.findSolid()
-
- edgeList = self.edges().vals()
- if len(edgeList) < 1:
- raise ValueError("Chamfer requires that edges be selected")
-
- s = solid.chamfer(length, length2, edgeList)
-
- solid.wrapped = s.wrapped
- return self.newObject([s])
-
-
-class Workplane(CQ):
- """
- Defines a coordinate system in space, in which 2-d coordinates can be used.
-
- :param plane: the plane in which the workplane will be done
- :type plane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right)
- :param origin: the desired origin of the new workplane
- :type origin: a 3-tuple in global coordinates, or None to default to the origin
- :param obj: an object to use initially for the stack
- :type obj: a CAD primitive, or None to use the centerpoint of the plane as the initial
- stack value.
- :raises: ValueError if the provided plane is not a plane, a valid named workplane
- :return: A Workplane object, with coordinate system matching the supplied plane.
-
- The most common use is::
-
- s = Workplane("XY")
-
- After creation, the stack contains a single point, the origin of the underlying plane,
- and the *current point* is on the origin.
-
- .. note::
- You can also create workplanes on the surface of existing faces using
- :py:meth:`CQ.workplane`
- """
-
- FOR_CONSTRUCTION = 'ForConstruction'
-
- def __init__(self, inPlane, origin=(0, 0, 0), obj=None):
- """
- make a workplane from a particular plane
-
- :param inPlane: the plane in which the workplane will be done
- :type inPlane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right)
- :param origin: the desired origin of the new workplane
- :type origin: a 3-tuple in global coordinates, or None to default to the origin
- :param obj: an object to use initially for the stack
- :type obj: a CAD primitive, or None to use the centerpoint of the plane as the initial
- stack value.
- :raises: ValueError if the provided plane is not a plane, or one of XY|YZ|XZ
- :return: A Workplane object, with coordinate system matching the supplied plane.
-
- The most common use is::
-
- s = Workplane("XY")
-
- After creation, the stack contains a single point, the origin of the underlying plane, and
- the *current point* is on the origin.
- """
-
- if inPlane.__class__.__name__ == 'Plane':
- tmpPlane = inPlane
- elif isinstance(inPlane, str) or isinstance(inPlane, unicode):
- tmpPlane = Plane.named(inPlane, origin)
- else:
- tmpPlane = None
-
- if tmpPlane is None:
- raise ValueError(
- 'Provided value {} is not a valid work plane'.format(inPlane))
-
- self.obj = obj
- self.plane = tmpPlane
- self.firstPoint = None
- # Changed so that workplane has the center as the first item on the stack
- self.objects = [self.plane.origin]
- self.parent = None
- self.ctx = CQContext()
-
- def transformed(self, rotate=(0, 0, 0), offset=(0, 0, 0)):
- """
- Create a new workplane based on the current one.
- The origin of the new plane is located at the existing origin+offset vector, where offset is
- given in coordinates local to the current plane
- The new plane is rotated through the angles specified by the components of the rotation
- vector.
- :param rotate: 3-tuple of angles to rotate, in degrees relative to work plane coordinates
- :param offset: 3-tuple to offset the new plane, in local work plane coordinates
- :return: a new work plane, transformed as requested
- """
-
- #old api accepted a vector, so we'll check for that.
- if rotate.__class__.__name__ == 'Vector':
- rotate = rotate.toTuple()
-
- if offset.__class__.__name__ == 'Vector':
- offset = offset.toTuple()
-
- p = self.plane.rotated(rotate)
- p.origin = self.plane.toWorldCoords(offset)
- ns = self.newObject([p.origin])
- ns.plane = p
-
- return ns
-
- def newObject(self, objlist):
- """
- Create a new workplane object from this one.
-
- Overrides CQ.newObject, and should be used by extensions, plugins, and
- subclasses to create new objects.
-
- :param objlist: new objects to put on the stack
- :type objlist: a list of CAD primitives
- :return: a new Workplane object with the current workplane as a parent.
- """
-
- #copy the current state to the new object
- ns = Workplane("XY")
- ns.plane = self.plane
- ns.parent = self
- ns.objects = list(objlist)
- ns.ctx = self.ctx
- return ns
-
- def _findFromPoint(self, useLocalCoords=False):
- """
- Finds the start point for an operation when an existing point
- is implied. Examples include 2d operations such as lineTo,
- which allows specifying the end point, and implicitly use the
- end of the previous line as the starting point
-
- :return: a Vector representing the point to use, or none if
- such a point is not available.
-
- :param useLocalCoords: selects whether the point is returned
- in local coordinates or global coordinates.
-
- The algorithm is this:
- * If an Edge is on the stack, its end point is used.yp
- * if a vector is on the stack, it is used
-
- WARNING: only the last object on the stack is used.
-
- NOTE:
- """
- obj = self.objects[-1]
-
- if isinstance(obj, Edge):
- p = obj.endPoint()
- elif isinstance(obj, Vector):
- p = obj
- else:
- raise RuntimeError("Cannot convert object type '%s' to vector " % type(obj))
-
- if useLocalCoords:
- return self.plane.toLocalCoords(p)
- else:
- return p
-
- def rarray(self, xSpacing, ySpacing, xCount, yCount, center=True):
- """
- Creates an array of points and pushes them onto the stack.
- If you want to position the array at another point, create another workplane
- that is shifted to the position you would like to use as a reference
-
- :param xSpacing: spacing between points in the x direction ( must be > 0)
- :param ySpacing: spacing between points in the y direction ( must be > 0)
- :param xCount: number of points ( > 0 )
- :param yCount: number of points ( > 0 )
- :param center: if true, the array will be centered at the center of the workplane. if
- false, the lower left corner will be at the center of the work plane
- """
-
- if xSpacing < 1 or ySpacing < 1 or xCount < 1 or yCount < 1:
- raise ValueError("Spacing and count must be > 0 ")
-
- lpoints = [] # coordinates relative to bottom left point
- for x in range(xCount):
- for y in range(yCount):
- lpoints.append((xSpacing * x, ySpacing * y))
-
- #shift points down and left relative to origin if requested
- if center:
- xc = xSpacing*(xCount-1) * 0.5
- yc = ySpacing*(yCount-1) * 0.5
- cpoints = []
- for p in lpoints:
- cpoints.append((p[0] - xc, p[1] - yc))
- lpoints = list(cpoints)
-
- return self.pushPoints(lpoints)
-
- def pushPoints(self, pntList):
- """
- Pushes a list of points onto the stack as vertices.
- The points are in the 2-d coordinate space of the workplane face
-
- :param pntList: a list of points to push onto the stack
- :type pntList: list of 2-tuples, in *local* coordinates
- :return: a new workplane with the desired points on the stack.
-
- A common use is to provide a list of points for a subsequent operation, such as creating
- circles or holes. This example creates a cube, and then drills three holes through it,
- based on three points::
-
- s = Workplane().box(1,1,1).faces(">Z").workplane().\
- pushPoints([(-0.3,0.3),(0.3,0.3),(0,0)])
- body = s.circle(0.05).cutThruAll()
-
- Here the circle function operates on all three points, and is then extruded to create three
- holes. See :py:meth:`circle` for how it works.
- """
- vecs = []
- for pnt in pntList:
- vec = self.plane.toWorldCoords(pnt)
- vecs.append(vec)
-
- return self.newObject(vecs)
-
- def center(self, x, y):
- """
- Shift local coordinates to the specified location.
-
- The location is specified in terms of local coordinates.
-
- :param float x: the new x location
- :param float y: the new y location
- :returns: the workplane object, with the center adjusted.
-
- The current point is set to the new center.
- This method is useful to adjust the center point after it has been created automatically on
- a face, but not where you'd like it to be.
-
- In this example, we adjust the workplane center to be at the corner of a cube, instead of
- the center of a face, which is the default::
-
- #this workplane is centered at x=0.5,y=0.5, the center of the upper face
- s = Workplane().box(1,1,1).faces(">Z").workplane()
-
- s.center(-0.5,-0.5) # move the center to the corner
- t = s.circle(0.25).extrude(0.2)
- assert ( t.faces().size() == 9 ) # a cube with a cylindrical nub at the top right corner
-
- 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])
- return n
-
- def lineTo(self, x, y, forConstruction=False):
- """
- Make a line from the current point to the provided point
-
- :param float x: the x point, in workplane plane coordinates
- :param float y: the y point, in workplane plane coordinates
- :return: the Workplane object with the current point at the end of the new line
-
- see :py:meth:`line` if you want to use relative dimensions to make a line instead.
- """
- startPoint = self._findFromPoint(False)
-
- endPoint = self.plane.toWorldCoords((x, y))
-
- p = Edge.makeLine(startPoint, endPoint)
-
- if not forConstruction:
- self._addPendingEdge(p)
-
- return self.newObject([p])
-
- # line a specified incremental amount from current point
- def line(self, xDist, yDist, forConstruction=False):
- """
- Make a line from the current point to the provided point, using
- dimensions relative to the current point
-
- :param float xDist: x distance from current point
- :param float yDist: y distance from current point
- :return: the workplane object with the current point at the end of the new line
-
- see :py:meth:`lineTo` if you want to use absolute coordinates to make a line instead.
- """
- p = self._findFromPoint(True) # return local coordinates
- return self.lineTo(p.x + xDist, yDist + p.y, forConstruction)
-
- def vLine(self, distance, forConstruction=False):
- """
- Make a vertical line from the current point the provided distance
-
- :param float distance: (y) distance from current point
- :return: the workplane object with the current point at the end of the new line
- """
- return self.line(0, distance, forConstruction)
-
- def hLine(self, distance, forConstruction=False):
- """
- Make a horizontal line from the current point the provided distance
-
- :param float distance: (x) distance from current point
- :return: the Workplane object with the current point at the end of the new line
- """
- return self.line(distance, 0, forConstruction)
-
- def vLineTo(self, yCoord, forConstruction=False):
- """
- Make a vertical line from the current point to the provided y coordinate.
-
- Useful if it is more convenient to specify the end location rather than distance,
- as in :py:meth:`vLine`
-
- :param float yCoord: y coordinate for the end of the line
- :return: the Workplane object with the current point at the end of the new line
- """
- p = self._findFromPoint(True)
- return self.lineTo(p.x, yCoord, forConstruction)
-
- def hLineTo(self, xCoord, forConstruction=False):
- """
- Make a horizontal line from the current point to the provided x coordinate.
-
- Useful if it is more convenient to specify the end location rather than distance,
- as in :py:meth:`hLine`
-
- :param float xCoord: x coordinate for the end of the line
- :return: the Workplane object with the current point at the end of the new line
- """
- p = self._findFromPoint(True)
- return self.lineTo(xCoord, p.y, forConstruction)
-
- #absolute move in current plane, not drawing
- def moveTo(self, x=0, y=0):
- """
- Move to the specified point, without drawing.
-
- :param x: desired x location, in local coordinates
- :type x: float, or none for zero
- :param y: desired y location, in local coordinates
- :type y: float, or none for zero.
-
- Not to be confused with :py:meth:`center`, which moves the center of the entire
- workplane, this method only moves the current point ( and therefore does not affect objects
- already drawn ).
-
- See :py:meth:`move` to do the same thing but using relative dimensions
- """
- newCenter = Vector(x, y, 0)
- return self.newObject([self.plane.toWorldCoords(newCenter)])
-
- #relative move in current plane, not drawing
- def move(self, xDist=0, yDist=0):
- """
- Move the specified distance from the current point, without drawing.
-
- :param xDist: desired x distance, in local coordinates
- :type xDist: float, or none for zero
- :param yDist: desired y distance, in local coordinates
- :type yDist: float, or none for zero.
-
- Not to be confused with :py:meth:`center`, which moves the center of the entire
- workplane, this method only moves the current point ( and therefore does not affect objects
- already drawn ).
-
- See :py:meth:`moveTo` to do the same thing but using absolute coordinates
- """
- p = self._findFromPoint(True)
- newCenter = p + Vector(xDist, yDist, 0)
- return self.newObject([self.plane.toWorldCoords(newCenter)])
-
- def spline(self, listOfXYTuple, forConstruction=False):
- """
- Create a spline interpolated through the provided points.
-
- :param listOfXYTuple: points to interpolate through
- :type listOfXYTuple: list of 2-tuple
- :return: a Workplane object with the current point at the end of the spline
-
- The spline will begin at the current point, and
- end with the last point in the XY tuple list
-
- This example creates a block with a spline for one side::
-
- s = Workplane(Plane.XY())
- sPnts = [
- (2.75,1.5),
- (2.5,1.75),
- (2.0,1.5),
- (1.5,1.0),
- (1.0,1.25),
- (0.5,1.0),
- (0,1.0)
- ]
- r = s.lineTo(3.0,0).lineTo(3.0,1.0).spline(sPnts).close()
- r = r.extrude(0.5)
-
- *WARNING* It is fairly easy to create a list of points
- that cannot be correctly interpreted as a spline.
-
- Future Enhancements:
- * provide access to control points
- """
- gstartPoint = self._findFromPoint(False)
- gEndPoint = self.plane.toWorldCoords(listOfXYTuple[-1])
-
- vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple]
- allPoints = [gstartPoint] + vecs
-
- e = Edge.makeSpline(allPoints)
-
- if not forConstruction:
- self._addPendingEdge(e)
-
- return self.newObject([e])
-
- def threePointArc(self, point1, point2, forConstruction=False):
- """
- Draw an arc from the current point, through point1, and ending at point2
-
- :param point1: point to draw through
- :type point1: 2-tuple, in workplane coordinates
- :param point2: end point for the arc
- :type point2: 2-tuple, in workplane coordinates
- :return: a workplane with the current point at the end of the arc
-
- Future Enhancements:
- provide a version that allows an arc using relative measures
- provide a centerpoint arc
- provide tangent arcs
- """
-
- gstartPoint = self._findFromPoint(False)
- gpoint1 = self.plane.toWorldCoords(point1)
- gpoint2 = self.plane.toWorldCoords(point2)
-
- arc = Edge.makeThreePointArc(gstartPoint, gpoint1, gpoint2)
-
- if not forConstruction:
- self._addPendingEdge(arc)
-
- return self.newObject([arc])
-
- def rotateAndCopy(self, matrix):
- """
- Makes a copy of all edges on the stack, rotates them according to the
- provided matrix, and then attempts to consolidate them into a single wire.
-
- :param matrix: a 4xr transformation matrix, in global coordinates
- :type matrix: a FreeCAD Base.Matrix object
- :return: a CadQuery object with consolidated wires, and any originals on the stack.
-
- The most common use case is to create a set of open edges, and then mirror them
- around either the X or Y axis to complete a closed shape.
-
- see :py:meth:`mirrorX` and :py:meth:`mirrorY` to mirror about the global X and Y axes
- see :py:meth:`mirrorX` and for an example
-
- Future Enhancements:
- faster implementation: this one transforms 3 times to accomplish the result
- """
-
- #convert edges to a wire, if there are pending edges
- n = self.wire(forConstruction=False)
-
- #attempt to consolidate wires together.
- consolidated = n.consolidateWires()
-
- rotatedWires = self.plane.rotateShapes(consolidated.wires().vals(), matrix)
-
- for w in rotatedWires:
- consolidated.objects.append(w)
- consolidated._addPendingWire(w)
-
- #attempt again to consolidate all of the wires
- c = consolidated.consolidateWires()
-
- return c
-
- def mirrorY(self):
- """
- Mirror entities around the y axis of the workplane plane.
-
- :return: a new object with any free edges consolidated into as few wires as possible.
-
- All free edges are collected into a wire, and then the wire is mirrored,
- and finally joined into a new wire
-
- Typically used to make creating wires with symmetry easier. This line of code::
-
- s = Workplane().lineTo(2,2).threePointArc((3,1),(2,0)).mirrorX().extrude(0.25)
-
- Produces a flat, heart shaped object
-
- Future Enhancements:
- mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness
- """
- tm = Matrix()
- tm.rotateY(math.pi)
- return self.rotateAndCopy(tm)
-
- def mirrorX(self):
- """
- Mirror entities around the x axis of the workplane plane.
-
- :return: a new object with any free edges consolidated into as few wires as possible.
-
- All free edges are collected into a wire, and then the wire is mirrored,
- and finally joined into a new wire
-
- Typically used to make creating wires with symmetry easier.
-
- Future Enhancements:
- mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness
- """
- tm = Matrix()
- tm.rotateX(math.pi)
- return self.rotateAndCopy(tm)
-
- def _addPendingEdge(self, edge):
- """
- Queues an edge for later combination into a wire.
-
- :param edge:
- :return:
- """
- self.ctx.pendingEdges.append(edge)
-
- if self.ctx.firstPoint is None:
- self.ctx.firstPoint = self.plane.toLocalCoords(edge.startPoint())
-
- def _addPendingWire(self, wire):
- """
- Queue a Wire for later extrusion
-
- Internal Processing Note. In FreeCAD, edges-->wires-->faces-->solids.
-
- but users do not normally care about these distinctions. Users 'think' in terms
- of edges, and solids.
-
- CadQuery tracks edges as they are drawn, and automatically combines them into wires
- when the user does an operation that needs it.
-
- Similarly, cadQuery tracks pending wires, and automatically combines them into faces
- when necessary to make a solid.
- """
- self.ctx.pendingWires.append(wire)
-
- def consolidateWires(self):
- """
- Attempt to consolidate wires on the stack into a single.
- If possible, a new object with the results are returned.
- if not possible, the wires remain separated
-
- FreeCAD has a bug in Part.Wire([]) which does not create wires/edges properly sometimes
- Additionally, it has a bug where a profile composed of two wires ( rather than one )
- also does not work properly. Together these are a real problem.
- """
- wires = self.wires().vals()
- if len(wires) < 2:
- return self
-
- #TODO: this makes the assumption that either all wires could be combined, or none.
- #in reality trying each combination of wires is probably not reasonable anyway
- w = Wire.combine(wires)
-
- #ok this is a little tricky. if we consolidate wires, we have to actually
- #modify the pendingWires collection to remove the original ones, and replace them
- #with the consolidate done
- #since we are already assuming that all wires could be consolidated, its easy, we just
- #clear the pending wire list
- r = self.newObject([w])
- r.ctx.pendingWires = []
- r._addPendingWire(w)
- return r
-
- def wire(self, forConstruction=False):
- """
- Returns a CQ object with all pending edges connected into a wire.
-
- All edges on the stack that can be combined will be combined into a single wire object,
- and other objects will remain on the stack unmodified
-
- :param forConstruction: whether the wire should be used to make a solid, or if it is just
- for reference
- :type forConstruction: boolean. true if the object is only for reference
-
- This method is primarily of use to plugin developers making utilities for 2-d construction.
- This method should be called when a user operation implies that 2-d construction is
- finished, and we are ready to begin working in 3d
-
- SEE '2-d construction concepts' for a more detailed explanation of how CadQuery handles
- edges, wires, etc
-
- Any non edges will still remain.
- """
-
- edges = self.ctx.pendingEdges
-
- #do not consolidate if there are no free edges
- if len(edges) == 0:
- return self
-
- self.ctx.pendingEdges = []
-
- others = []
- for e in self.objects:
- if type(e) != Edge:
- others.append(e)
-
-
- w = Wire.assembleEdges(edges)
- if not forConstruction:
- self._addPendingWire(w)
-
- return self.newObject(others + [w])
-
- def each(self, callBackFunction, useLocalCoordinates=False):
- """
- Runs the provided function on each value in the stack, and collects the return values into
- a new CQ object.
-
- Special note: a newly created workplane always has its center point as its only stack item
-
- :param callBackFunction: the function to call for each item on the current stack.
- :param useLocalCoordinates: should values be converted from local coordinates first?
- :type useLocalCoordinates: boolean
-
- The callback function must accept one argument, which is the item on the stack, and return
- one object, which is collected. If the function returns None, nothing is added to the stack.
- The object passed into the callBackFunction is potentially transformed to local coordinates,
- if useLocalCoordinates is true
-
- useLocalCoordinates is very useful for plugin developers.
-
- If false, the callback function is assumed to be working in global coordinates. Objects
- created are added as-is, and objects passed into the function are sent in using global
- coordinates
-
- If true, the calling function is assumed to be working in local coordinates. Objects are
- transformed to local coordinates before they are passed into the callback method, and result
- objects are transformed to global coordinates after they are returned.
-
- This allows plugin developers to create objects in local coordinates, without worrying
- about the fact that the working plane is different than the global coordinate system.
-
-
- TODO: wrapper object for Wire will clean up forConstruction flag everywhere
- """
- results = []
- for obj in self.objects:
-
- if useLocalCoordinates:
- #TODO: this needs to work for all types of objects, not just vectors!
- r = callBackFunction(self.plane.toLocalCoords(obj))
- r = r.transformShape(self.plane.rG)
- else:
- r = callBackFunction(obj)
-
- if type(r) == Wire:
- if not r.forConstruction:
- self._addPendingWire(r)
-
- results.append(r)
-
- return self.newObject(results)
-
- def eachpoint(self, callbackFunction, useLocalCoordinates=False):
- """
- Same as each(), except each item on the stack is converted into a point before it
- is passed into the callback function.
-
- :return: CadQuery object which contains a list of vectors (points ) on its stack.
-
- :param useLocalCoordinates: should points be in local or global coordinates
- :type useLocalCoordinates: boolean
-
- The resulting object has a point on the stack for each object on the original stack.
- Vertices and points remain a point. Faces, Wires, Solids, Edges, and Shells are converted
- to a point by using their center of mass.
-
- If the stack has zero length, a single point is returned, which is the center of the current
- workplane/coordinate system
- """
- #convert stack to a list of points
- pnts = []
- if len(self.objects) == 0:
- #nothing on the stack. here, we'll assume we should operate with the
- #origin as the context point
- pnts.append(self.plane.origin)
- else:
-
- for v in self.objects:
- pnts.append(v.Center())
-
- return self.newObject(pnts).each(callbackFunction, useLocalCoordinates)
-
- def rect(self, xLen, yLen, centered=True, forConstruction=False):
- """
- Make a rectangle for each item on the stack.
-
- :param xLen: length in xDirection ( in workplane coordinates )
- :type xLen: float > 0
- :param yLen: length in yDirection ( in workplane coordinates )
- :type yLen: float > 0
- :param boolean centered: true if the rect is centered on the reference point, false if the
- lower-left is on the reference point
- :param forConstruction: should the new wires be reference geometry only?
- :type forConstruction: true if the wires are for reference, false if they are creating part
- geometry
- :return: a new CQ object with the created wires on the stack
-
- A common use case is to use a for-construction rectangle to define the centers of a hole
- pattern::
-
- s = Workplane().rect(4.0,4.0,forConstruction=True).vertices().circle(0.25)
-
- Creates 4 circles at the corners of a square centered on the origin.
-
- Future Enhancements:
- better way to handle forConstruction
- project points not in the workplane plane onto the workplane plane
- """
- def makeRectangleWire(pnt):
- # Here pnt is in local coordinates due to useLocalCoords=True
- # (xc,yc,zc) = pnt.toTuple()
- if centered:
- p1 = pnt.add(Vector(xLen/-2.0, yLen/-2.0, 0))
- p2 = pnt.add(Vector(xLen/2.0, yLen/-2.0, 0))
- p3 = pnt.add(Vector(xLen/2.0, yLen/2.0, 0))
- p4 = pnt.add(Vector(xLen/-2.0, yLen/2.0, 0))
- else:
- p1 = pnt
- p2 = pnt.add(Vector(xLen, 0, 0))
- p3 = pnt.add(Vector(xLen, yLen, 0))
- p4 = pnt.add(Vector(0, yLen, 0))
-
- w = Wire.makePolygon([p1, p2, p3, p4, p1], forConstruction)
- return w
- #return Part.makePolygon([p1,p2,p3,p4,p1])
-
- return self.eachpoint(makeRectangleWire, True)
-
- #circle from current point
- def circle(self, radius, forConstruction=False):
- """
- Make a circle for each item on the stack.
-
- :param radius: radius of the circle
- :type radius: float > 0
- :param forConstruction: should the new wires be reference geometry only?
- :type forConstruction: true if the wires are for reference, false if they are creating
- part geometry
- :return: a new CQ object with the created wires on the stack
-
- A common use case is to use a for-construction rectangle to define the centers of a
- hole pattern::
-
- s = Workplane().rect(4.0,4.0,forConstruction=True).vertices().circle(0.25)
-
- Creates 4 circles at the corners of a square centered on the origin. Another common case is
- to use successive circle() calls to create concentric circles. This works because the
- center of a circle is its reference point::
-
- s = Workplane().circle(2.0).circle(1.0)
-
- Creates two concentric circles, which when extruded will form a ring.
-
- Future Enhancements:
- better way to handle forConstruction
- project points not in the workplane plane onto the workplane plane
-
- """
- def makeCircleWire(obj):
- cir = Wire.makeCircle(radius, obj, Vector(0, 0, 1))
- cir.forConstruction = forConstruction
- return cir
-
- return self.eachpoint(makeCircleWire, useLocalCoordinates=True)
-
- def polygon(self, nSides, diameter, forConstruction=False):
- """
- Creates a polygon inscribed in a circle of the specified diameter for each point on
- the stack
-
- The first vertex is always oriented in the x direction.
-
- :param nSides: number of sides, must be > 3
- :param diameter: the size of the circle the polygon is inscribed into
- :return: a polygon wire
- """
- def _makePolygon(center):
- #pnt is a vector in local coordinates
- angle = 2.0 * math.pi / nSides
- pnts = []
- for i in range(nSides+1):
- pnts.append(center + Vector((diameter / 2.0 * math.cos(angle*i)),
- (diameter / 2.0 * math.sin(angle*i)), 0))
- return Wire.makePolygon(pnts, forConstruction)
-
- return self.eachpoint(_makePolygon, True)
-
- def polyline(self, listOfXYTuple, forConstruction=False):
- """
- Create a polyline from a list of points
-
- :param listOfXYTuple: a list of points in Workplane coordinates
- :type listOfXYTuple: list of 2-tuples
- :param forConstruction: whether or not the edges are used for reference
- :type forConstruction: true if the edges are for reference, false if they are for creating geometry
- part geometry
- :return: a new CQ object with a list of edges on the stack
-
- *NOTE* most commonly, the resulting wire should be closed.
- """
-
- # Our list of new edges that will go into a new CQ object
- edges = []
-
- # The very first startPoint comes from our original object, but not after that
- startPoint = self._findFromPoint(False)
-
- # Draw a line for each set of points, starting from the from-point of the original CQ object
- for curTuple in listOfXYTuple:
- endPoint = self.plane.toWorldCoords(curTuple)
-
- edges.append(Edge.makeLine(startPoint, endPoint))
-
- # We need to move the start point for the next line that we draw or we get stuck at the same startPoint
- startPoint = endPoint
-
- if not forConstruction:
- self._addPendingEdge(edges[-1])
-
- return self.newObject(edges)
-
- def close(self):
- """
- End 2-d construction, and attempt to build a closed wire.
-
- :return: a CQ object with a completed wire on the stack, if possible.
-
- After 2-d drafting with lineTo,threePointArc, and polyline, it is necessary
- to convert the edges produced by these into one or more wires.
-
- When a set of edges is closed, cadQuery assumes it is safe to build the group of edges
- into a wire. This example builds a simple triangular prism::
-
- s = Workplane().lineTo(1,0).lineTo(1,1).close().extrude(0.2)
- """
- self.lineTo(self.ctx.firstPoint.x, self.ctx.firstPoint.y)
-
- # Need to reset the first point after closing a wire
- self.ctx.firstPoint=None
-
- return self.wire()
-
- def largestDimension(self):
- """
- Finds the largest dimension in the stack.
- Used internally to create thru features, this is how you can compute
- how long or wide a feature must be to make sure to cut through all of the material
- :return: A value representing the largest dimension of the first solid on the stack
- """
- #TODO: this implementation is naive and returns the dims of the first solid... most of
- #TODO: the time this works. but a stronger implementation would be to search all solids.
- s = self.findSolid()
- if s:
- return s.BoundingBox().DiagonalLength * 5.0
- else:
- return -1
-
- def cutEach(self, fcn, useLocalCoords=False, clean=True):
- """
- Evaluates the provided function at each point on the stack (ie, eachpoint)
- and then cuts the result from the context solid.
- :param fcn: a function suitable for use in the eachpoint method: ie, that accepts
- a vector
- :param useLocalCoords: same as for :py:meth:`eachpoint`
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :return: a CQ object that contains the resulting solid
- :raises: an error if there is not a context solid to cut from
- """
- ctxSolid = self.findSolid()
- if ctxSolid is None:
- raise ValueError("Must have a solid in the chain to cut from!")
-
- #will contain all of the counterbores as a single compound
- results = self.eachpoint(fcn, useLocalCoords).vals()
- s = ctxSolid
- for cb in results:
- s = s.cut(cb)
-
- if clean: s = s.clean()
-
- ctxSolid.wrapped = s.wrapped
- return self.newObject([s])
-
- #but parameter list is different so a simple function pointer wont work
- def cboreHole(self, diameter, cboreDiameter, cboreDepth, depth=None, clean=True):
- """
- Makes a counterbored hole for each item on the stack.
-
- :param diameter: the diameter of the hole
- :type diameter: float > 0
- :param cboreDiameter: the diameter of the cbore
- :type cboreDiameter: float > 0 and > diameter
- :param cboreDepth: depth of the counterbore
- :type cboreDepth: float > 0
- :param depth: the depth of the hole
- :type depth: float > 0 or None to drill thru the entire part.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
-
- The surface of the hole is at the current workplane plane.
-
- One hole is created for each item on the stack. A very common use case is to use a
- construction rectangle to define the centers of a set of holes, like so::
-
- s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane()\
- .rect(1.5,3.5,forConstruction=True)\
- .vertices().cboreHole(0.125, 0.25,0.125,depth=None)
-
- This sample creates a plate with a set of holes at the corners.
-
- **Plugin Note**: this is one example of the power of plugins. Counterbored holes are quite
- time consuming to create, but are quite easily defined by users.
-
- see :py:meth:`cskHole` to make countersinks instead of counterbores
- """
- if depth is None:
- depth = self.largestDimension()
-
- def _makeCbore(center):
- """
- Makes a single hole with counterbore at the supplied point
- returns a solid suitable for subtraction
- pnt is in local coordinates
- """
- boreDir = Vector(0, 0, -1)
- #first make the hole
- hole = Solid.makeCylinder(diameter/2.0, depth, center, boreDir) # local coordianates!
-
- #add the counter bore
- cbore = Solid.makeCylinder(cboreDiameter / 2.0, cboreDepth, center, boreDir)
- r = hole.fuse(cbore)
- return r
-
- return self.cutEach(_makeCbore, True, clean)
-
- #TODO: almost all code duplicated!
- #but parameter list is different so a simple function pointer wont work
- def cskHole(self, diameter, cskDiameter, cskAngle, depth=None, clean=True):
- """
- Makes a countersunk hole for each item on the stack.
-
- :param diameter: the diameter of the hole
- :type diameter: float > 0
- :param cskDiameter: the diameter of the countersink
- :type cskDiameter: float > 0 and > diameter
- :param cskAngle: angle of the countersink, in degrees ( 82 is common )
- :type cskAngle: float > 0
- :param depth: the depth of the hole
- :type depth: float > 0 or None to drill thru the entire part.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
-
- The surface of the hole is at the current workplane.
-
- One hole is created for each item on the stack. A very common use case is to use a
- construction rectangle to define the centers of a set of holes, like so::
-
- s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane()\
- .rect(1.5,3.5,forConstruction=True)\
- .vertices().cskHole(0.125, 0.25,82,depth=None)
-
- This sample creates a plate with a set of holes at the corners.
-
- **Plugin Note**: this is one example of the power of plugins. CounterSunk holes are quite
- time consuming to create, but are quite easily defined by users.
-
- see :py:meth:`cboreHole` to make counterbores instead of countersinks
- """
-
- if depth is None:
- depth = self.largestDimension()
-
- def _makeCsk(center):
- #center is in local coordinates
-
- boreDir = Vector(0, 0, -1)
-
- #first make the hole
- hole = Solid.makeCylinder(diameter/2.0, depth, center, boreDir) # local coords!
- r = cskDiameter / 2.0
- h = r / math.tan(math.radians(cskAngle / 2.0))
- csk = Solid.makeCone(r, 0.0, h, center, boreDir)
- r = hole.fuse(csk)
- return r
-
- return self.cutEach(_makeCsk, True, clean)
-
- #TODO: almost all code duplicated!
- #but parameter list is different so a simple function pointer wont work
- def hole(self, diameter, depth=None, clean=True):
- """
- Makes a hole for each item on the stack.
-
- :param diameter: the diameter of the hole
- :type diameter: float > 0
- :param depth: the depth of the hole
- :type depth: float > 0 or None to drill thru the entire part.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
-
- The surface of the hole is at the current workplane.
-
- One hole is created for each item on the stack. A very common use case is to use a
- construction rectangle to define the centers of a set of holes, like so::
-
- s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane()\
- .rect(1.5,3.5,forConstruction=True)\
- .vertices().hole(0.125, 0.25,82,depth=None)
-
- This sample creates a plate with a set of holes at the corners.
-
- **Plugin Note**: this is one example of the power of plugins. CounterSunk holes are quite
- time consuming to create, but are quite easily defined by users.
-
- see :py:meth:`cboreHole` and :py:meth:`cskHole` to make counterbores or countersinks
- """
- if depth is None:
- depth = self.largestDimension()
-
- def _makeHole(center):
- """
- Makes a single hole with counterbore at the supplied point
- returns a solid suitable for subtraction
- pnt is in local coordinates
- """
- boreDir = Vector(0, 0, -1)
- #first make the hole
- hole = Solid.makeCylinder(diameter / 2.0, depth, center, boreDir) # local coordinates!
- return hole
-
- return self.cutEach(_makeHole, True, clean)
-
- #TODO: duplicated code with _extrude and extrude
- def twistExtrude(self, distance, angleDegrees, combine=True, clean=True):
- """
- Extrudes a wire in the direction normal to the plane, but also twists by the specified
- angle over the length of the extrusion
-
- The center point of the rotation will be the center of the workplane
-
- See extrude for more details, since this method is the same except for the the addition
- of the angle. In fact, if angle=0, the result is the same as a linear extrude.
-
- **NOTE** This method can create complex calculations, so be careful using it with
- complex geometries
-
- :param distance: the distance to extrude normal to the workplane
- :param angle: angline ( in degrees) to rotate through the extrusion
- :param boolean combine: True to combine the resulting solid with parent solids if found.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :return: a CQ object with the resulting solid selected.
- """
- #group wires together into faces based on which ones are inside the others
- #result is a list of lists
- wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
-
- self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion
-
- #compute extrusion vector and extrude
- eDir = self.plane.zDir.multiply(distance)
-
- #one would think that fusing faces into a compound and then extruding would work,
- #but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc)
- #but then cutting it from the main solid fails with BRep_NotDone.
- #the work around is to extrude each and then join the resulting solids, which seems to work
-
- #underlying cad kernel can only handle simple bosses-- we'll aggregate them if there
- # are multiple sets
- r = None
- for ws in wireSets:
- thisObj = Solid.extrudeLinearWithRotation(ws[0], ws[1:], self.plane.origin,
- eDir, angleDegrees)
- if r is None:
- r = thisObj
- else:
- r = r.fuse(thisObj)
-
- if combine:
- newS = self._combineWithBase(r)
- else:
- newS = self.newObject([r])
- if clean: newS = newS.clean()
- return newS
-
- def extrude(self, distance, combine=True, clean=True):
- """
- Use all un-extruded wires in the parent chain to create a prismatic solid.
-
- :param distance: the distance to extrude, normal to the workplane plane
- :type distance: float, negative means opposite the normal direction
- :param boolean combine: True to combine the resulting solid with parent solids if found.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :return: a CQ object with the resulting solid selected.
-
- extrude always *adds* material to a part.
-
- The returned object is always a CQ object, and depends on wither combine is True, and
- whether a context solid is already defined:
-
- * if combine is False, the new value is pushed onto the stack.
- * if combine is true, the value is combined with the context solid if it exists,
- and the resulting solid becomes the new context solid.
-
- FutureEnhancement:
- Support for non-prismatic extrusion ( IE, sweeping along a profile, not just
- perpendicular to the plane extrude to surface. this is quite tricky since the surface
- selected may not be planar
- """
- r = self._extrude(distance) # returns a Solid (or a compound if there were multiple)
- if combine:
- newS = self._combineWithBase(r)
- else:
- newS = self.newObject([r])
- if clean: newS = newS.clean()
- return newS
-
- def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True, clean=True):
- """
- Use all un-revolved wires in the parent chain to create a solid.
-
- :param angleDegrees: the angle to revolve through.
- :type angleDegrees: float, anything less than 360 degrees will leave the shape open
- :param axisStart: the start point of the axis of rotation
- :type axisStart: tuple, a two tuple
- :param axisEnd: the end point of the axis of rotation
- :type axisEnd: tuple, a two tuple
- :param combine: True to combine the resulting solid with parent solids if found.
- :type combine: boolean, combine with parent solid
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :return: a CQ object with the resulting solid selected.
-
- The returned object is always a CQ object, and depends on wither combine is True, and
- whether a context solid is already defined:
-
- * if combine is False, the new value is pushed onto the stack.
- * if combine is true, the value is combined with the context solid if it exists,
- and the resulting solid becomes the new context solid.
- """
- #Make sure we account for users specifying angles larger than 360 degrees
- angleDegrees %= 360.0
-
- #Compensate for FreeCAD not assuming that a 0 degree revolve means a 360 degree revolve
- angleDegrees = 360.0 if angleDegrees == 0 else angleDegrees
-
- # The default start point of the vector defining the axis of rotation will be the origin
- # of the workplane
- if axisStart is None:
- axisStart = self.plane.toWorldCoords((0, 0)).toTuple()
- else:
- axisStart = self.plane.toWorldCoords(axisStart).toTuple()
-
- # The default end point of the vector defining the axis of rotation should be along the
- # normal from the plane
- if axisEnd is None:
- # Make sure we match the user's assumed axis of rotation if they specified an start
- # but not an end
- if axisStart[1] != 0:
- axisEnd = self.plane.toWorldCoords((0, axisStart[1])).toTuple()
- else:
- axisEnd = self.plane.toWorldCoords((0, 1)).toTuple()
- else:
- axisEnd = self.plane.toWorldCoords(axisEnd).toTuple()
-
- # returns a Solid (or a compound if there were multiple)
- r = self._revolve(angleDegrees, axisStart, axisEnd)
- if combine:
- newS = self._combineWithBase(r)
- else:
- newS = self.newObject([r])
- if clean: newS = newS.clean()
- return newS
-
- def _combineWithBase(self, obj):
- """
- Combines the provided object with the base solid, if one can be found.
- :param obj:
- :return: a new object that represents the result of combining the base object with obj,
- or obj if one could not be found
- """
- baseSolid = self.findSolid(searchParents=True)
- r = obj
- if baseSolid is not None:
- r = baseSolid.fuse(obj)
- baseSolid.wrapped = r.wrapped
-
- return self.newObject([r])
-
- def combine(self, clean=True):
- """
- Attempts to combine all of the items on the stack into a single item.
- WARNING: all of the items must be of the same type!
-
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
- :raises: ValueError if there are no items on the stack, or if they cannot be combined
- :return: a CQ object with the resulting object selected
- """
- items = list(self.objects)
- s = items.pop(0)
- for ss in items:
- s = s.fuse(ss)
-
- if clean: s = s.clean()
-
- return self.newObject([s])
-
- def union(self, toUnion=None, combine=True, clean=True):
- """
- Unions all of the items on the stack of toUnion with the current solid.
- If there is no current solid, the items in toUnion are unioned together.
- if combine=True, the result and the original are updated to point to the new object
- if combine=False, the result will be on the stack, but the original is unmodified
-
- :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
- :raises: ValueError if there is no solid to add to in the chain
- :return: a CQ object with the resulting object selected
- """
-
- #first collect all of the items together
- if type(toUnion) == CQ or type(toUnion) == Workplane:
- solids = toUnion.solids().vals()
- if len(solids) < 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) == Solid:
- 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 combine and solidRef is not None:
- r = solidRef.fuse(newS)
- solidRef.wrapped = newS.wrapped
- else:
- r = newS
-
- if clean: r = r.clean()
-
- return self.newObject([r])
-
- def cut(self, toCut, combine=True, clean=True):
- """
- Cuts the provided solid from the current solid, IE, perform a solid subtraction
-
- if combine=True, the result and the original are updated to point to the new object
- if combine=False, the result will be on the stack, but the original is unmodified
-
- :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
- :raises: ValueError if there is no solid to subtract from in the chain
- :return: a CQ object with the resulting object selected
- """
-
- # look for parents to cut from
- solidRef = self.findSolid(searchStack=True, searchParents=True)
-
- if solidRef is None:
- raise ValueError("Cannot find solid to cut from")
- solidToCut = None
- if type(toCut) == CQ or type(toCut) == Workplane:
- solidToCut = toCut.val()
- elif type(toCut) == Solid:
- solidToCut = toCut
- else:
- raise ValueError("Cannot cut type '{}'".format(type(toCut)))
-
- newS = solidRef.cut(solidToCut)
-
- if clean: newS = newS.clean()
-
- if combine:
- solidRef.wrapped = newS.wrapped
-
- return self.newObject([newS])
-
- def cutBlind(self, distanceToCut, clean=True):
- """
- Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid.
-
- Similar to extrude, except that a solid in the parent chain is required to remove material
- from. cutBlind always removes material from a part.
-
- :param distanceToCut: distance to extrude before cutting
- :type distanceToCut: float, >0 means in the positive direction of the workplane normal,
- <0 means in the negative direction
- :param boolean 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
-
- see :py:meth:`cutThruAll` to cut material from the entire part
-
- Future Enhancements:
- Cut Up to Surface
- """
- #first, make the object
- toCut = self._extrude(distanceToCut)
-
- #now find a solid in the chain
-
- solidRef = self.findSolid()
-
- s = solidRef.cut(toCut)
-
- if clean: s = s.clean()
-
- solidRef.wrapped = s.wrapped
- return self.newObject([s])
-
- def cutThruAll(self, positive=False, clean=True):
- """
- Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid.
-
- Similar to extrude, except that a solid in the parent chain is required to remove material
- from. cutThruAll always removes material from a part.
-
- :param boolean positive: True to cut in the positive direction, false to cut in the
- negative direction
- :param boolean 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
-
- see :py:meth:`cutBlind` to cut material to a limited depth
- """
- maxDim = self.largestDimension()
- if not positive:
- maxDim *= (-1.0)
-
- return self.cutBlind(maxDim, clean)
-
- def loft(self, filled=True, ruled=False, combine=True):
- """
- Make a lofted solid, through the set of wires.
- :return: a CQ object containing the created loft
- """
- wiresToLoft = self.ctx.pendingWires
- self.ctx.pendingWires = []
-
- r = Solid.makeLoft(wiresToLoft, ruled)
-
- if combine:
- parentSolid = self.findSolid(searchStack=False, searchParents=True)
- if parentSolid is not None:
- r = parentSolid.fuse(r)
- parentSolid.wrapped = r.wrapped
-
- return self.newObject([r])
-
- def _extrude(self, distance):
- """
- Make a prismatic solid from the existing set of pending wires.
-
- :param distance: distance to extrude
- :return: a FreeCAD solid, suitable for boolean operations.
-
- This method is a utility method, primarily for plugin and internal use.
- It is the basis for cutBlind,extrude,cutThruAll, and all similar methods.
-
- Future Enhancements:
- extrude along a profile (sweep)
- """
-
- #group wires together into faces based on which ones are inside the others
- #result is a list of lists
- s = time.time()
- wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
- #print "sorted wires in %d sec" % ( time.time() - s )
- self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion
-
- #compute extrusion vector and extrude
- eDir = self.plane.zDir.multiply(distance)
-
-
- #one would think that fusing faces into a compound and then extruding would work,
- #but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc)
- #but then cutting it from the main solid fails with BRep_NotDone.
- #the work around is to extrude each and then join the resulting solids, which seems to work
-
- # underlying cad kernel can only handle simple bosses-- we'll aggregate them if there are
- # multiple sets
-
- # IMPORTANT NOTE: OCC is slow slow slow in boolean operations. So you do NOT want to fuse
- # each item to another and save the result-- instead, you want to combine all of the new
- # items into a compound, and fuse them together!!!
- # r = None
- # for ws in wireSets:
- # thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
- # if r is None:
- # r = thisObj
- # else:
- # s = time.time()
- # r = r.fuse(thisObj)
- # print "Fused in %0.3f sec" % ( time.time() - s )
- # return r
-
- toFuse = []
- for ws in wireSets:
- thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
- toFuse.append(thisObj)
-
- return Compound.makeCompound(toFuse)
-
- def _revolve(self, angleDegrees, axisStart, axisEnd):
- """
- Make a solid from the existing set of pending wires.
-
- :param angleDegrees: the angle to revolve through.
- :type angleDegrees: float, anything less than 360 degrees will leave the shape open
- :param axisStart: the start point of the axis of rotation
- :type axisStart: tuple, a two tuple
- :param axisEnd: the end point of the axis of rotation
- :type axisEnd: tuple, a two tuple
- :return: a FreeCAD solid, suitable for boolean operations.
-
- This method is a utility method, primarily for plugin and internal use.
- """
- #We have to gather the wires to be revolved
- wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
-
- #Mark that all of the wires have been used to create a revolution
- self.ctx.pendingWires = []
-
- #Revolve the wires, make a compound out of them and then fuse them
- toFuse = []
- for ws in wireSets:
- thisObj = Solid.revolve(ws[0], ws[1:], angleDegrees, axisStart, axisEnd)
- toFuse.append(thisObj)
-
- return Compound.makeCompound(toFuse)
-
- def box(self, length, width, height, centered=(True, True, True), combine=True, clean=True):
- """
- Return a 3d box with specified dimensions for each object on the stack.
-
- :param length: box size in X direction
- :type length: float > 0
- :param width: box size in Y direction
- :type width: float > 0
- :param height: box size in Z direction
- :type height: float > 0
- :param centered: should the box be centered, or should reference point be at the lower
- bound of the range?
- :param combine: should the results be combined with other solids on the stack
- (and each other)?
- :type combine: true to combine shapes, false otherwise.
- :param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
-
- Centered is a tuple that describes whether the box should be centered on the x,y, and
- z axes. If true, the box is centered on the respective axis relative to the workplane
- origin, if false, the workplane center will represent the lower bound of the resulting box
-
- one box is created for each item on the current stack. If no items are on the stack, one box
- using the current workplane center is created.
-
- If combine is true, the result will be a single object on the stack:
- if a solid was found in the chain, the result is that solid with all boxes produced
- fused onto it otherwise, the result is the combination of all the produced boxes
-
- if combine is false, the result will be a list of the boxes produced
-
- Most often boxes form the basis for a part::
-
- #make a single box with lower left corner at origin
- s = Workplane().box(1,2,3,centered=(False,False,False)
-
- But sometimes it is useful to create an array of them:
-
- #create 4 small square bumps on a larger base plate:
- s = Workplane().box(4,4,0.5).faces(">Z").workplane()\
- .rect(3,3,forConstruction=True).vertices().box(0.25,0.25,0.25,combine=True)
-
- """
-
- def _makebox(pnt):
-
- #(xp,yp,zp) = self.plane.toLocalCoords(pnt)
- (xp, yp, zp) = pnt.toTuple()
- if centered[0]:
- xp -= (length / 2.0)
- if centered[1]:
- yp -= (width / 2.0)
- if centered[2]:
- zp -= (height / 2.0)
-
- return Solid.makeBox(length, width, height, Vector(xp, yp, zp))
-
- boxes = self.eachpoint(_makebox, True)
-
- #if combination is not desired, just return the created boxes
- if not combine:
- return boxes
- else:
- #combine everything
- return self.union(boxes, clean=clean)
-
- def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360,
- centered=(True, True, True), combine=True, clean=True):
- """
- Returns a 3D sphere with the specified radius for each point on the stack
-
- :param radius: The radius of the sphere
- :type radius: float > 0
- :param direct: The direction axis for the creation of the sphere
- :type direct: A three-tuple
- :param angle1: The first angle to sweep the sphere arc through
- :type angle1: float > 0
- :param angle2: The second angle to sweep the sphere arc through
- :type angle2: float > 0
- :param angle3: The third angle to sweep the sphere arc through
- :type angle3: float > 0
- :param centered: A three-tuple of booleans that determines whether the sphere is centered
- on each axis origin
- :param combine: Whether the results should be combined with other solids on the stack
- (and each other)
- :type combine: true to combine shapes, false otherwise
- :return: A sphere object for each point on the stack
-
- Centered is a tuple that describes whether the sphere should be centered on the x,y, and
- z axes. If true, the sphere is centered on the respective axis relative to the workplane
- origin, if false, the workplane center will represent the lower bound of the resulting
- sphere.
-
- One sphere is created for each item on the current stack. If no items are on the stack, one
- box using the current workplane center is created.
-
- If combine is true, the result will be a single object on the stack:
- If a solid was found in the chain, the result is that solid with all spheres produced
- fused onto it otherwise, the result is the combination of all the produced boxes
-
- If combine is false, the result will be a list of the spheres produced
- """
-
- # Convert the direction tuple to a vector, if needed
- if isinstance(direct, tuple):
- direct = Vector(direct)
-
- def _makesphere(pnt):
- """
- Inner function that is used to create a sphere for each point/object on the workplane
- :param pnt: The center point for the sphere
- :return: A CQ Solid object representing a sphere
- """
- (xp, yp, zp) = pnt.toTuple()
-
- if not centered[0]:
- xp += radius
-
- if not centered[1]:
- yp += radius
-
- if not centered[2]:
- zp += radius
-
- return Solid.makeSphere(radius, Vector(xp, yp, zp), direct, angle1, angle2, angle3)
-
- # We want a sphere for each point on the workplane
- spheres = self.eachpoint(_makesphere, True)
-
- # If we don't need to combine everything, just return the created spheres
- if not combine:
- return spheres
- else:
- return self.union(spheres, clean=clean)
-
- def clean(self):
- """
- Cleans the current solid by removing unwanted edges from the
- faces.
-
- Normally you don't have to call this function. It is
- automatically called after each related operation. You can
- disable this behavior with `clean=False` parameter if method
- has any. In some cases this can improve performance
- drastically but is generally dis-advised since it may break
- some operations such as fillet.
-
- Note that in some cases where lots of solid operations are
- chained, `clean()` may actually improve performance since
- the shape is 'simplified' at each step and thus next operation
- is easier.
-
- Also note that, due to limitation of the underlying engine,
- `clean` may fail to produce a clean output in some cases such as
- spherical faces.
- """
- try:
- cleanObjects = [obj.clean() for obj in self.objects]
- except AttributeError:
- raise AttributeError("%s object doesn't support `clean()` method!" % obj.ShapeType())
- return self.newObject(cleanObjects)
diff --git a/CadQuery/Libs/cadquery/README.txt b/CadQuery/Libs/cadquery/README.txt
deleted file mode 100644
index ab8dc7e..0000000
--- a/CadQuery/Libs/cadquery/README.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-***
-Core CadQuery implementation.
-
-No files should depend on or import FreeCAD , pythonOCC, or other CAD Kernel libraries!!!
-Dependencies should be on the classes provided by implementation packages, which in turn
-can depend on CAD libraries.
-
-***
\ No newline at end of file
diff --git a/CadQuery/Libs/cadquery/__init__.py b/CadQuery/Libs/cadquery/__init__.py
deleted file mode 100644
index d3676a3..0000000
--- a/CadQuery/Libs/cadquery/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#these items point to the freecad implementation
-from .freecad_impl.geom import Plane,BoundBox,Vector,Matrix,sortWiresByBuildOrder
-from .freecad_impl.shapes import Shape,Vertex,Edge,Face,Wire,Solid,Shell,Compound
-from .freecad_impl import exporters
-from .freecad_impl import importers
-
-#these items are the common implementation
-
-#the order of these matter
-from .selectors import NearestToPointSelector,ParallelDirSelector,DirectionSelector,PerpendicularDirSelector,TypeSelector,DirectionMinMaxSelector,StringSyntaxSelector,Selector
-from .CQ import CQ,CQContext,Workplane
-
-
-__all__ = [
- 'CQ','Workplane','plugins','selectors','Plane','BoundBox','Matrix','Vector','sortWiresByBuildOrder',
- 'Shape','Vertex','Edge','Wire','Solid','Shell','Compound','exporters', 'importers', 'NearestToPointSelector','ParallelDirSelector','DirectionSelector','PerpendicularDirSelector','TypeSelector','DirectionMinMaxSelector','StringSyntaxSelector','Selector','plugins'
-]
-
-__version__ = "0.1.8"
diff --git a/CadQuery/Libs/cadquery/cheatsheet/cadquery_cheatsheet.html b/CadQuery/Libs/cadquery/cheatsheet/cadquery_cheatsheet.html
deleted file mode 100644
index bb58252..0000000
--- a/CadQuery/Libs/cadquery/cheatsheet/cadquery_cheatsheet.html
+++ /dev/null
@@ -1,404 +0,0 @@
-
-
-
A connection between two or more vertices along a particular path (called a curve)
-
-
-
wire
-
A collection of edges that are connected together
-
-
-
face
-
A set of edges or wires that enclose a surface
-
-
-
shell
-
A collection of faces that are connected together along some of their edges
-
-
-
solid
-
A shell that has a closed interior
-
-
-
compound
-
A collection of solids
-
-
-
-
-
Named Planes
- Available named planes are as follows. Direction references refer to the global directions.
-
-
-
Name
-
xDir
-
yDir
-
zDir
-
-
-
XY
-
+x
-
+y
-
+z
-
-
-
YZ
-
+y
-
+z
-
+x
-
-
-
XZ
-
+x
-
+z
-
-y
-
-
-
front
-
+x
-
+y
-
+z
-
-
-
back
-
-x
-
+y
-
-z
-
-
-
left
-
+z
-
+y
-
-x
-
-
-
right
-
-z
-
+y
-
+x
-
-
-
top
-
+x
-
-z
-
+y
-
-
-
bottom
-
+x
-
+z
-
-y
-
-
-
-
-
Core Classes
-
-
-
Class
-
Description
-
-
-
CQ(obj)
-
Provides enhanced functionality for a wrapped CAD primitive.
-
-
-
Plane(origin, xDir, normal)
-
A 2d coordinate system in space, with the x-y axes on the a plane, and a particular point as the origin.
-
-
-
Workplane(inPlane[origin, obj])
-
Defines a coordinate system in space, in which 2-d coordinates can be used.
-
-
-
-
-
-
-
Selector Methods
- CadQuery selector strings allow filtering various types of object lists.
- Most commonly, Edges, Faces, and Vertices are used, but all objects types can be filtered.
-
- All types of filters work on faces. In most cases, the selector refers to the direction of the normal vector of the face.
- If a face is not planar, selectors are evaluated at the center of mass of the face. This can lead to results that are quite unexpected.
-
-
-
Selector
-
Selector Class
-
Selects
-
# Objects Returned
-
-
-
+Z
-
DirectionSelector
-
Faces with normal in +z direction
-
0 or 1
-
-
-
|Z
-
ParallelDirSelector
-
Faces parallel to xy plane
-
0..many
-
-
-
-X
-
DirectionSelector
-
Faces with normal in neg x direction
-
0..many
-
-
-
#Z
-
PerpendicularDirSelector
-
Faces perpendicular to z direction
-
0..many
-
-
-
%Plane
-
TypeSelector
-
Faces of type plane
-
0..many
-
-
-
>Y
-
DirectionMinMaxSelector
-
Face farthest in the positive y dir
-
0 or 1
-
-
-
<Y
-
DirectionMinMaxSelector
-
Face farthest in the negative y dir
-
0 or 1
-
-
-
-
-
Examples of Filtering Edges
- Some filter types are not supported for edges. The selector usually refers to the direction of the edge.
- Non-linear edges are not selected for any selectors except type (%). Non-linear edges are never returned when these filters are applied.
-
-
-
Selector
-
Selector Class
-
Selects
-
# Objects Returned
-
-
-
+Z
-
DirectionSelector
-
Edges aligned in the Z direction
-
0..many
-
-
-
|Z
-
ParallelDirSelector
-
Edges parallel to z direction
-
0..many
-
-
-
-X
-
DirectionSelector
-
Edges aligned in neg x direction
-
0..many
-
-
-
#Z
-
PerpendicularDirSelector
-
Edges perpendicular to z direction
-
0..many
-
-
-
%Plane
-
TypeSelector
-
Edges type line
-
0..many
-
-
-
>Y
-
DirectionMinMaxSelector
-
Edges farthest in the positive y dir
-
0 or 1
-
-
-
<Y
-
DirectionMinMaxSelector
-
Edges farthest in the negative y dir
-
0 or 1
-
-
-
-
-
Examples of Filtering Vertices
- Only a few of the filter types apply to vertices. The location of the vertex is the subject of the filter.
-
-
-
Selector
-
Selector Class
-
Selects
-
# Objects Returned
-
-
-
>Y
-
DirectionMinMaxSelector
-
Vertices farthest in the positive y dir
-
0 or 1
-
-
-
<Y
-
DirectionMinMaxSelector
-
Vertices farthest in the negative y dir
-
0 or 1
-
-
-
-
-
-
diff --git a/CadQuery/Libs/cadquery/contrib/__init__.py b/CadQuery/Libs/cadquery/contrib/__init__.py
deleted file mode 100644
index 67c7b68..0000000
--- a/CadQuery/Libs/cadquery/contrib/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
-"""
diff --git a/CadQuery/Libs/cadquery/cq_directive.py b/CadQuery/Libs/cadquery/cq_directive.py
deleted file mode 100644
index d898d27..0000000
--- a/CadQuery/Libs/cadquery/cq_directive.py
+++ /dev/null
@@ -1,85 +0,0 @@
-"""
-A special directive for including a cq object.
-
-"""
-
-import sys, os, shutil, imp, warnings, cStringIO, re,traceback
-
-from cadquery import *
-import StringIO
-from docutils.parsers.rst import directives
-
-
-template = """
-
-.. raw:: html
-
-
- %(outSVG)s
-
-
-
-
-"""
-template_content_indent = ' '
-
-
-def cq_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
-
- #only consider inline snippets
- plot_code = '\n'.join(content)
-
- # Since we don't have a filename, use a hash based on the content
- #the script must define a variable called 'out', which is expected to
- #be a CQ object
- outSVG = "Your Script Did not assign the 'result' variable!"
-
-
- try:
- _s = StringIO.StringIO()
- exec(plot_code)
-
- exporters.exportShape(result,"SVG",_s)
- outSVG = _s.getvalue()
- except:
- traceback.print_exc()
- outSVG = traceback.format_exc()
-
- #now out
- # Now start generating the lines of output
- lines = []
-
- #get rid of new lines
- outSVG = outSVG.replace('\n','')
-
- txtAlign = "left"
- if options.has_key("align"):
- txtAlign = options['align']
-
- lines.extend((template % locals()).split('\n'))
-
- lines.extend(['::', ''])
- lines.extend([' %s' % row.rstrip()
- for row in plot_code.split('\n')])
- lines.append('')
-
- if len(lines):
- state_machine.insert_input(
- lines, state_machine.input_lines.source(0))
-
- return []
-
-def setup(app):
- setup.app = app
- setup.config = app.config
- setup.confdir = app.confdir
-
- options = {'height': directives.length_or_unitless,
- 'width': directives.length_or_percentage_or_unitless,
- 'align': directives.unchanged
- }
-
- app.add_directive('cq_plot', cq_directive, True, (0, 2, 0), **options)
-
-
diff --git a/CadQuery/Libs/cadquery/freecad_impl/README.txt b/CadQuery/Libs/cadquery/freecad_impl/README.txt
deleted file mode 100644
index 34ea788..0000000
--- a/CadQuery/Libs/cadquery/freecad_impl/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-It is ok for files in this directory to import FreeCAD, FreeCAD.Base, and FreeCAD.Part.
-
-Other modules should _not_ depend on FreeCAD
\ No newline at end of file
diff --git a/CadQuery/Libs/cadquery/freecad_impl/__init__.py b/CadQuery/Libs/cadquery/freecad_impl/__init__.py
deleted file mode 100644
index 3e041e3..0000000
--- a/CadQuery/Libs/cadquery/freecad_impl/__init__.py
+++ /dev/null
@@ -1,112 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
-"""
-import os, sys
-
-
-def _fc_path():
- """Find FreeCAD"""
- _PATH = ""
- if _PATH:
- return _PATH
-
- #look for FREECAD_LIB env variable
- if os.environ.has_key('FREECAD_LIB'):
- _PATH = os.environ.get('FREECAD_LIB')
- if os.path.exists( _PATH):
- return _PATH
-
- if sys.platform.startswith('linux'):
- #Make some dangerous assumptions...
- for _PATH in [
- os.path.join(os.path.expanduser("~"), "lib/freecad/lib"),
- "/usr/local/lib/freecad/lib",
- "/usr/lib/freecad/lib",
- "/opt/freecad/lib/",
- "/usr/bin/freecad/lib",
- "/usr/lib/freecad",
- ]:
- if os.path.exists(_PATH):
- return _PATH
-
- elif sys.platform.startswith('win'):
- #try all the usual suspects
- for _PATH in [
- "c:/Program Files/FreeCAD0.12/bin",
- "c:/Program Files/FreeCAD0.13/bin",
- "c:/Program Files/FreeCAD0.14/bin",
- "c:/Program Files/FreeCAD0.15/bin",
- "c:/Program Files/FreeCAD0.16/bin",
- "c:/Program Files/FreeCAD0.17/bin",
- "c:/Program Files (x86)/FreeCAD0.12/bin",
- "c:/Program Files (x86)/FreeCAD0.13/bin",
- "c:/Program Files (x86)/FreeCAD0.14/bin",
- "c:/Program Files (x86)/FreeCAD0.15/bin",
- "c:/Program Files (x86)/FreeCAD0.16/bin",
- "c:/Program Files (x86)/FreeCAD0.17/bin",
- "c:/apps/FreeCAD0.12/bin",
- "c:/apps/FreeCAD0.13/bin",
- "c:/apps/FreeCAD0.14/bin",
- "c:/apps/FreeCAD0.15/bin",
- "c:/apps/FreeCAD0.16/bin",
- "c:/apps/FreeCAD0.17/bin",
- "c:/Program Files/FreeCAD 0.12/bin",
- "c:/Program Files/FreeCAD 0.13/bin",
- "c:/Program Files/FreeCAD 0.14/bin",
- "c:/Program Files/FreeCAD 0.15/bin",
- "c:/Program Files/FreeCAD 0.16/bin",
- "c:/Program Files/FreeCAD 0.17/bin",
- "c:/Program Files (x86)/FreeCAD 0.12/bin",
- "c:/Program Files (x86)/FreeCAD 0.13/bin",
- "c:/Program Files (x86)/FreeCAD 0.14/bin",
- "c:/Program Files (x86)/FreeCAD 0.15/bin",
- "c:/Program Files (x86)/FreeCAD 0.16/bin",
- "c:/Program Files (x86)/FreeCAD 0.17/bin",
- "c:/apps/FreeCAD 0.12/bin",
- "c:/apps/FreeCAD 0.13/bin",
- "c:/apps/FreeCAD 0.14/bin",
- "c:/apps/FreeCAD 0.15/bin",
- "c:/apps/FreeCAD 0.16/bin",
- "c:/apps/FreeCAD 0.17/bin",
- ]:
- if os.path.exists(_PATH):
- return _PATH
- elif sys.platform.startswith('darwin'):
- #Assume we're dealing with a Mac
- for _PATH in [
- "/Applications/FreeCAD.app/Contents/lib",
- os.path.join(os.path.expanduser("~"), "Library/Application Support/FreeCAD/lib"),
- ]:
- if os.path.exists(_PATH):
- return _PATH
-
-
-
-#Make sure that the correct FreeCAD path shows up in Python's system path
-no_library_path = ImportError('cadquery was unable to determine freecads library path')
-try:
- import FreeCAD
-except ImportError:
- path = _fc_path()
- if path:
- sys.path.insert(0, path)
- try:
- import FreeCAD
- except ImportError:
- raise no_library_path
- else: raise no_library_path
diff --git a/CadQuery/Libs/cadquery/freecad_impl/exporters.py b/CadQuery/Libs/cadquery/freecad_impl/exporters.py
deleted file mode 100644
index 8ba681b..0000000
--- a/CadQuery/Libs/cadquery/freecad_impl/exporters.py
+++ /dev/null
@@ -1,413 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
-
- An exporter should provide functionality to accept a shape, and return
- a string containing the model content.
-"""
-import cadquery
-
-import FreeCAD
-import Drawing
-
-import tempfile, os, StringIO
-
-
-try:
- import xml.etree.cElementTree as ET
-except ImportError:
- import xml.etree.ElementTree as ET
-
-
-class ExportTypes:
- STL = "STL"
- STEP = "STEP"
- AMF = "AMF"
- SVG = "SVG"
- TJS = "TJS"
-
-
-class UNITS:
- MM = "mm"
- IN = "in"
-
-
-def toString(shape, exportType, tolerance=0.1):
- s = StringIO.StringIO()
- exportShape(shape, exportType, s, tolerance)
- return s.getvalue()
-
-
-def exportShape(shape,exportType,fileLike,tolerance=0.1):
- """
- :param shape: the shape to export. it can be a shape object, or a cadquery object. If a cadquery
- object, the first value is exported
- :param exportFormat: the exportFormat to use
- :param tolerance: the tolerance, in model units
- :param fileLike: a file like object to which the content will be written.
- The object should be already open and ready to write. The caller is responsible
- for closing the object
- """
-
-
- if isinstance(shape,cadquery.CQ):
- shape = shape.val()
-
- if exportType == ExportTypes.TJS:
- #tessellate the model
- tess = shape.tessellate(tolerance)
-
- mesher = JsonMesh() #warning: needs to be changed to remove buildTime and exportTime!!!
- #add vertices
- for vec in tess[0]:
- mesher.addVertex(vec.x, vec.y, vec.z)
-
- #add faces
- for f in tess[1]:
- mesher.addTriangleFace(f[0],f[1], f[2])
- fileLike.write( mesher.toJson())
- elif exportType == ExportTypes.SVG:
- fileLike.write(getSVG(shape.wrapped))
- elif exportType == ExportTypes.AMF:
- tess = shape.tessellate(tolerance)
- aw = AmfWriter(tess).writeAmf(fileLike)
- else:
-
- #all these types required writing to a file and then
- #re-reading. this is due to the fact that FreeCAD writes these
- (h, outFileName) = tempfile.mkstemp()
- #weird, but we need to close this file. the next step is going to write to
- #it from c code, so it needs to be closed.
- os.close(h)
-
- if exportType == ExportTypes.STEP:
- shape.exportStep(outFileName)
- elif exportType == ExportTypes.STL:
- shape.wrapped.exportStl(outFileName)
- else:
- raise ValueError("No idea how i got here")
-
- res = readAndDeleteFile(outFileName)
- fileLike.write(res)
-
-def readAndDeleteFile(fileName):
- """
- read data from file provided, and delete it when done
- return the contents as a string
- """
- res = ""
- with open(fileName,'r') as f:
- res = f.read()
-
- os.remove(fileName)
- return res
-
-
-def guessUnitOfMeasure(shape):
- """
- Guess the unit of measure of a shape.
- """
- bb = shape.BoundBox
-
- dimList = [ bb.XLength, bb.YLength,bb.ZLength ]
- #no real part would likely be bigger than 10 inches on any side
- if max(dimList) > 10:
- return UNITS.MM
-
- #no real part would likely be smaller than 0.1 mm on all dimensions
- if min(dimList) < 0.1:
- return UNITS.IN
-
- #no real part would have the sum of its dimensions less than about 5mm
- if sum(dimList) < 10:
- return UNITS.IN
-
- return UNITS.MM
-
-
-class AmfWriter(object):
- def __init__(self,tessellation):
-
- self.units = "mm"
- self.tessellation = tessellation
-
- def writeAmf(self,outFile):
- amf = ET.Element('amf',units=self.units)
- #TODO: if result is a compound, we need to loop through them
- object = ET.SubElement(amf,'object',id="0")
- mesh = ET.SubElement(object,'mesh')
- vertices = ET.SubElement(mesh,'vertices')
- volume = ET.SubElement(mesh,'volume')
-
- #add vertices
- for v in self.tessellation[0]:
- vtx = ET.SubElement(vertices,'vertex')
- coord = ET.SubElement(vtx,'coordinates')
- x = ET.SubElement(coord,'x')
- x.text = str(v.x)
- y = ET.SubElement(coord,'y')
- y.text = str(v.y)
- z = ET.SubElement(coord,'z')
- z.text = str(v.z)
-
- #add triangles
- for t in self.tessellation[1]:
- triangle = ET.SubElement(volume,'triangle')
- v1 = ET.SubElement(triangle,'v1')
- v1.text = str(t[0])
- v2 = ET.SubElement(triangle,'v2')
- v2.text = str(t[1])
- v3 = ET.SubElement(triangle,'v3')
- v3.text = str(t[2])
-
-
- ET.ElementTree(amf).write(outFile,encoding='ISO-8859-1')
-
-"""
- Objects that represent
- three.js JSON object notation
- https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
-"""
-class JsonMesh(object):
- def __init__(self):
-
- self.vertices = [];
- self.faces = [];
- self.nVertices = 0;
- self.nFaces = 0;
-
- def addVertex(self,x,y,z):
- self.nVertices += 1;
- self.vertices.extend([x,y,z]);
-
- #add triangle composed of the three provided vertex indices
- def addTriangleFace(self, i,j,k):
- #first position means justa simple triangle
- self.nFaces += 1;
- self.faces.extend([0,int(i),int(j),int(k)]);
-
- """
- Get a json model from this model.
- For now we'll forget about colors, vertex normals, and all that stuff
- """
- def toJson(self):
- return JSON_TEMPLATE % {
- 'vertices' : str(self.vertices),
- 'faces' : str(self.faces),
- 'nVertices': self.nVertices,
- 'nFaces' : self.nFaces
- };
-
-
-def getPaths(freeCadSVG):
- """
- freeCad svg is worthless-- except for paths, which are fairly useful
- this method accepts svg from fReeCAD and returns a list of strings suitable for inclusion in a path element
- returns two lists-- one list of visible lines, and one list of hidden lines
-
- HACK ALERT!!!!!
- FreeCAD does not give a way to determine which lines are hidden and which are not
- the only way to tell is that hidden lines are in a with 0.15 stroke and visible are 0.35 stroke.
- so we actually look for that as a way to parse.
-
- to make it worse, elementTree xpath attribute selectors do not work in python 2.6, and we
- cannot use python 2.7 due to freecad. So its necessary to look for the pure strings! ick!
- """
-
- hiddenPaths = []
- visiblePaths = []
- if len(freeCadSVG) > 0:
- #yuk, freecad returns svg fragments. stupid stupid
- fullDoc = "%s" % freeCadSVG
- e = ET.ElementTree(ET.fromstring(fullDoc))
- segments = e.findall(".//g")
- for s in segments:
- paths = s.findall("path")
-
- if s.get("stroke-width") == "0.15": #hidden line HACK HACK HACK
- mylist = hiddenPaths
- else:
- mylist = visiblePaths
-
- for p in paths:
- mylist.append(p.get("d"))
- return (hiddenPaths,visiblePaths)
- else:
- return ([],[])
-
-
-def getSVG(shape,opts=None):
- """
- Export a shape to SVG
- """
-
- d = {'width':800,'height':240,'marginLeft':200,'marginTop':20}
-
- if opts:
- d.update(opts)
-
- #need to guess the scale and the coordinate center
- uom = guessUnitOfMeasure(shape)
-
- width=float(d['width'])
- height=float(d['height'])
- marginLeft=float(d['marginLeft'])
- marginTop=float(d['marginTop'])
-
- #TODO: provide option to give 3 views
- viewVector = FreeCAD.Base.Vector(-1.75,1.1,5)
- (visibleG0,visibleG1,hiddenG0,hiddenG1) = Drawing.project(shape,viewVector)
-
- (hiddenPaths,visiblePaths) = getPaths(Drawing.projectToSVG(shape,viewVector,"ShowHiddenLines")) #this param is totally undocumented!
-
- #get bounding box -- these are all in 2-d space
- bb = visibleG0.BoundBox
- bb.add(visibleG1.BoundBox)
- bb.add(hiddenG0.BoundBox)
- bb.add(hiddenG1.BoundBox)
-
- #width pixels for x, height pixesl for y
- unitScale = min( width / bb.XLength * 0.75 , height / bb.YLength * 0.75 )
-
- #compute amount to translate-- move the top left into view
- (xTranslate,yTranslate) = ( (0 - bb.XMin) + marginLeft/unitScale ,(0- bb.YMax) - marginTop/unitScale)
-
- #compute paths ( again -- had to strip out freecad crap )
- hiddenContent = ""
- for p in hiddenPaths:
- hiddenContent += PATHTEMPLATE % p
-
- visibleContent = ""
- for p in visiblePaths:
- visibleContent += PATHTEMPLATE % p
-
- svg = SVG_TEMPLATE % (
- {
- "unitScale" : str(unitScale),
- "strokeWidth" : str(1.0/unitScale),
- "hiddenContent" : hiddenContent ,
- "visibleContent" :visibleContent,
- "xTranslate" : str(xTranslate),
- "yTranslate" : str(yTranslate),
- "width" : str(width),
- "height" : str(height),
- "textboxY" :str(height - 30),
- "uom" : str(uom)
- }
- )
- #svg = SVG_TEMPLATE % (
- # {"content": projectedContent}
- #)
- return svg
-
-
-def exportSVG(shape, fileName):
- """
- accept a cadquery shape, and export it to the provided file
- TODO: should use file-like objects, not a fileName, and/or be able to return a string instead
- export a view of a part to svg
- """
-
- svg = getSVG(shape.val().wrapped)
- f = open(fileName,'w')
- f.write(svg)
- f.close()
-
-
-
-JSON_TEMPLATE= """\
-{
- "metadata" :
- {
- "formatVersion" : 3,
- "generatedBy" : "ParametricParts",
- "vertices" : %(nVertices)d,
- "faces" : %(nFaces)d,
- "normals" : 0,
- "colors" : 0,
- "uvs" : 0,
- "materials" : 1,
- "morphTargets" : 0
- },
-
- "scale" : 1.0,
-
- "materials": [ {
- "DbgColor" : 15658734,
- "DbgIndex" : 0,
- "DbgName" : "Material",
- "colorAmbient" : [0.0, 0.0, 0.0],
- "colorDiffuse" : [0.6400000190734865, 0.10179081114814892, 0.126246120426746],
- "colorSpecular" : [0.5, 0.5, 0.5],
- "shading" : "Lambert",
- "specularCoef" : 50,
- "transparency" : 1.0,
- "vertexColors" : false
- }],
-
- "vertices": %(vertices)s,
-
- "morphTargets": [],
-
- "normals": [],
-
- "colors": [],
-
- "uvs": [[]],
-
- "faces": %(faces)s
-}
-"""
-
-SVG_TEMPLATE = """
-
-"""
-
-PATHTEMPLATE="\t\t\t\n"
-
diff --git a/CadQuery/Libs/cadquery/freecad_impl/geom.py b/CadQuery/Libs/cadquery/freecad_impl/geom.py
deleted file mode 100644
index 37ea922..0000000
--- a/CadQuery/Libs/cadquery/freecad_impl/geom.py
+++ /dev/null
@@ -1,642 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
-"""
-
-import math
-import cadquery
-import FreeCAD
-import Part as FreeCADPart
-
-
-def sortWiresByBuildOrder(wireList, plane, result=[]):
- """Tries to determine how wires should be combined into faces.
-
- Assume:
- The wires make up one or more faces, which could have 'holes'
- Outer wires are listed ahead of inner wires
- there are no wires inside wires inside wires
- ( IE, islands -- we can deal with that later on )
- none of the wires are construction wires
- Compute:
- one or more sets of wires, with the outer wire listed first, and inner
- ones
- Returns, list of lists.
- """
- result = []
-
- remainingWires = list(wireList)
- while remainingWires:
- outerWire = remainingWires.pop(0)
- group = [outerWire]
- otherWires = list(remainingWires)
- for w in otherWires:
- if plane.isWireInside(outerWire, w):
- group.append(w)
- remainingWires.remove(w)
- result.append(group)
-
- return result
-
-
-class Vector(object):
- """Create a 3-dimensional vector
-
- :param *args: a 3-d vector, with x-y-z parts.
-
- you can either provide:
- * nothing (in which case the null vector is return)
- * a FreeCAD vector
- * a vector ( in which case it is copied )
- * a 3-tuple
- * three float values, x, y, and z
- """
- def __init__(self, *args):
- if len(args) == 3:
- fV = FreeCAD.Base.Vector(args[0], args[1], args[2])
- elif len(args) == 1:
- if isinstance(args[0], Vector):
- fV = args[0].wrapped
- elif isinstance(args[0], tuple):
- fV = FreeCAD.Base.Vector(args[0][0], args[0][1], args[0][2])
- elif isinstance(args[0], FreeCAD.Base.Vector):
- fV = args[0]
- else:
- fV = args[0]
- elif len(args) == 0:
- fV = FreeCAD.Base.Vector(0, 0, 0)
- else:
- raise ValueError("Expected three floats, FreeCAD Vector, or 3-tuple")
-
- self._wrapped = fV
-
- @property
- def x(self):
- return self.wrapped.x
-
- @property
- def y(self):
- return self.wrapped.y
-
- @property
- def z(self):
- return self.wrapped.z
-
- @property
- def Length(self):
- return self.wrapped.Length
-
- @property
- def wrapped(self):
- return self._wrapped
-
- def toTuple(self):
- return (self.x, self.y, self.z)
-
- # TODO: is it possible to create a dynamic proxy without all this code?
- def cross(self, v):
- return Vector(self.wrapped.cross(v.wrapped))
-
- def dot(self, v):
- return self.wrapped.dot(v.wrapped)
-
- def sub(self, v):
- return Vector(self.wrapped.sub(v.wrapped))
-
- def add(self, v):
- return Vector(self.wrapped.add(v.wrapped))
-
- def multiply(self, scale):
- """Return a copy multiplied by the provided scalar"""
- tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
- return Vector(tmp_fc_vector.multiply(scale))
-
- def normalize(self):
- """Return a normalized version of this vector"""
- tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
- tmp_fc_vector.normalize()
- return Vector(tmp_fc_vector)
-
- def Center(self):
- """Return the vector itself
-
- The center of myself is myself.
- Provided so that vectors, vertexes, and other shapes all support a
- common interface, when Center() is requested for all objects on the
- stack.
- """
- return self
-
- def getAngle(self, v):
- return self.wrapped.getAngle(v.wrapped)
-
- def distanceToLine(self):
- raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
-
- def projectToLine(self):
- raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
-
- def distanceToPlane(self):
- raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
-
- def projectToPlane(self):
- raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
-
- def __add__(self, v):
- return self.add(v)
-
- def __repr__(self):
- return self.wrapped.__repr__()
-
- def __str__(self):
- return self.wrapped.__str__()
-
- def __ne__(self, other):
- return self.wrapped.__ne__(other)
-
- def __eq__(self, other):
- return self.wrapped.__eq__(other)
-
-
-class Matrix:
- """A 3d , 4x4 transformation matrix.
-
- Used to move geometry in space.
- """
- def __init__(self, matrix=None):
- if matrix is None:
- self.wrapped = FreeCAD.Base.Matrix()
- else:
- self.wrapped = matrix
-
- def rotateX(self, angle):
- self.wrapped.rotateX(angle)
-
- def rotateY(self, angle):
- self.wrapped.rotateY(angle)
-
-
-class Plane(object):
- """A 2D coordinate system in space
-
- A 2D coordinate system in space, with the x-y axes on the plane, and a
- particular point as the origin.
-
- A plane allows the use of 2-d coordinates, which are later converted to
- global, 3d coordinates when the operations are complete.
-
- Frequently, it is not necessary to create work planes, as they can be
- created automatically from faces.
- """
-
- @classmethod
- def named(cls, stdName, origin=(0, 0, 0)):
- """Create a predefined Plane based on the conventional names.
-
- :param stdName: one of (XY|YZ|ZX|XZ|YX|ZY|front|back|left|right|top|bottom)
- :type stdName: string
- :param origin: the desired origin, specified in global coordinates
- :type origin: 3-tuple of the origin of the new plane, in global coorindates.
-
- Available named planes are as follows. Direction references refer to
- the global directions.
-
- =========== ======= ======= ======
- Name xDir yDir zDir
- =========== ======= ======= ======
- XY +x +y +z
- YZ +y +z +x
- ZX +z +x +y
- XZ +x +z -y
- YX +y +x -z
- ZY +z +y -x
- front +x +y +z
- back -x +y -z
- left +z +y -x
- right -z +y +x
- top +x -z +y
- bottom +x +z -y
- =========== ======= ======= ======
- """
-
- namedPlanes = {
- # origin, xDir, normal
- 'XY': Plane(origin, (1, 0, 0), (0, 0, 1)),
- 'YZ': Plane(origin, (0, 1, 0), (1, 0, 0)),
- 'ZX': Plane(origin, (0, 0, 1), (0, 1, 0)),
- 'XZ': Plane(origin, (1, 0, 0), (0, -1, 0)),
- 'YX': Plane(origin, (0, 1, 0), (0, 0, -1)),
- 'ZY': Plane(origin, (0, 0, 1), (-1, 0, 0)),
- 'front': Plane(origin, (1, 0, 0), (0, 0, 1)),
- 'back': Plane(origin, (-1, 0, 0), (0, 0, -1)),
- 'left': Plane(origin, (0, 0, 1), (-1, 0, 0)),
- 'right': Plane(origin, (0, 0, -1), (1, 0, 0)),
- 'top': Plane(origin, (1, 0, 0), (0, 1, 0)),
- 'bottom': Plane(origin, (1, 0, 0), (0, -1, 0))
- }
-
- try:
- return namedPlanes[stdName]
- except KeyError:
- raise ValueError('Supported names are {}'.format(
- namedPlanes.keys()))
-
- @classmethod
- def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('XY', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def YZ(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
- plane = Plane.named('YZ', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def ZX(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
- plane = Plane.named('ZX', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def XZ(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('XZ', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def YX(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
- plane = Plane.named('YX', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def ZY(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
- plane = Plane.named('ZY', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def front(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('front', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def back(cls, origin=(0, 0, 0), xDir=Vector(-1, 0, 0)):
- plane = Plane.named('back', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def left(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
- plane = Plane.named('left', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def right(cls, origin=(0, 0, 0), xDir=Vector(0, 0, -1)):
- plane = Plane.named('right', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def top(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('top', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- @classmethod
- def bottom(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
- plane = Plane.named('bottom', origin)
- plane._setPlaneDir(xDir)
- return plane
-
- def __init__(self, origin, xDir, normal):
- """Create a Plane with an arbitrary orientation
-
- TODO: project x and y vectors so they work even if not orthogonal
- :param origin: the origin
- :type origin: a three-tuple of the origin, in global coordinates
- :param xDir: a vector representing the xDirection.
- :type xDir: a three-tuple representing a vector, or a FreeCAD Vector
- :param normal: the normal direction for the new plane
- :type normal: a FreeCAD Vector
- :raises: ValueError if the specified xDir is not orthogonal to the provided normal.
- :return: a plane in the global space, with the xDirection of the plane in the specified direction.
- """
- normal = Vector(normal)
- if (normal.Length == 0.0):
- raise ValueError('normal should be non null')
- self.zDir = normal.normalize()
- xDir = Vector(xDir)
- if (xDir.Length == 0.0):
- raise ValueError('xDir should be non null')
- self._setPlaneDir(xDir)
-
- self.invZDir = self.zDir.multiply(-1.0)
-
- self.origin = origin
-
- @property
- def origin(self):
- return self._origin
-
- @origin.setter
- def origin(self, value):
- self._origin = Vector(value)
- self._calcTransforms()
-
- def setOrigin2d(self, x, y):
- """Set a new origin in the plane itself
-
- Set a new origin in the plane itself. The plane's orientation and
- xDrection are unaffected.
-
- :param float x: offset in the x direction
- :param float y: offset in the y direction
- :return: void
-
- The new coordinates are specified in terms of the current 2-d system.
- As an example:
- p = Plane.XY()
- p.setOrigin2d(2, 2)
- p.setOrigin2d(2, 2)
- results in a plane with its origin at (x, y) = (4, 4) in global
- coordinates. Both operations were relative to local coordinates of the
- plane.
- """
- self.origin = self.toWorldCoords((x, y))
-
- def isWireInside(self, baseWire, testWire):
- """Determine if testWire is inside baseWire
-
- Determine if testWire is inside baseWire, after both wires are projected
- into the current plane.
-
- :param baseWire: a reference wire
- :type baseWire: a FreeCAD wire
- :param testWire: another wire
- :type testWire: a FreeCAD wire
- :return: True if testWire is inside baseWire, otherwise False
-
- If either wire does not lie in the current plane, it is projected into
- the plane first.
-
- *WARNING*: This method is not 100% reliable. It uses bounding box
- tests, but needs more work to check for cases when curves are complex.
-
- Future Enhancements:
- * Discretizing points along each curve to provide a more reliable
- test.
- """
- # TODO: also use a set of points along the wire to test as well.
- # TODO: would it be more efficient to create objects in the local
- # coordinate system, and then transform to global
- # coordinates upon extrusion?
-
- tBaseWire = baseWire.transformGeometry(self.fG)
- tTestWire = testWire.transformGeometry(self.fG)
-
- # These bounding boxes will have z=0, since we transformed them into the
- # space of the plane.
- bb = tBaseWire.BoundingBox()
- tb = tTestWire.BoundingBox()
-
- # findOutsideBox actually inspects both ways, here we only want to
- # know if one is inside the other
- return bb == BoundBox.findOutsideBox2D(bb, tb)
-
- def toLocalCoords(self, obj):
- """Project the provided coordinates onto this plane
-
- :param obj: an object or vector to convert
- :type vector: a vector or shape
- :return: an object of the same type, but converted to local coordinates
-
-
- Most of the time, the z-coordinate returned will be zero, because most
- operations based on a plane are all 2-d. Occasionally, though, 3-d
- points outside of the current plane are transformed. One such example is
- :py:meth:`Workplane.box`, where 3-d corners of a box are transformed to
- orient the box in space correctly.
-
- """
- if isinstance(obj, Vector):
- return Vector(self.fG.multiply(obj.wrapped))
- elif isinstance(obj, cadquery.Shape):
- return obj.transformShape(self.rG)
- else:
- raise ValueError(
- "Don't know how to convert type {} to local coordinates".format(
- type(obj)))
-
- def toWorldCoords(self, tuplePoint):
- """Convert a point in local coordinates to global coordinates
-
- :param tuplePoint: point in local coordinates to convert.
- :type tuplePoint: a 2 or three tuple of float. The third value is taken to be zero if not supplied.
- :return: a Vector in global coordinates
- """
- if isinstance(tuplePoint, Vector):
- v = tuplePoint
- elif len(tuplePoint) == 2:
- v = Vector(tuplePoint[0], tuplePoint[1], 0)
- else:
- v = Vector(tuplePoint)
- return Vector(self.rG.multiply(v.wrapped))
-
- def rotated(self, rotate=(0, 0, 0)):
- """Returns a copy of this plane, rotated about the specified axes
-
- Since the z axis is always normal the plane, rotating around Z will
- always produce a plane that is parallel to this one.
-
- The origin of the workplane is unaffected by the rotation.
-
- Rotations are done in order x, y, z. If you need a different order,
- manually chain together multiple rotate() commands.
-
- :param rotate: Vector [xDegrees, yDegrees, zDegrees]
- :return: a copy of this plane rotated as requested.
- """
- rotate = Vector(rotate)
- # Convert to radians.
- rotate = rotate.multiply(math.pi / 180.0)
-
- # Compute rotation matrix.
- m = FreeCAD.Base.Matrix()
- m.rotateX(rotate.x)
- m.rotateY(rotate.y)
- m.rotateZ(rotate.z)
-
- # Compute the new plane.
- newXdir = Vector(m.multiply(self.xDir.wrapped))
- newZdir = Vector(m.multiply(self.zDir.wrapped))
-
- return Plane(self.origin, newXdir, newZdir)
-
- def rotateShapes(self, listOfShapes, rotationMatrix):
- """Rotate the listOfShapes by the supplied rotationMatrix
-
- @param listOfShapes is a list of shape objects
- @param rotationMatrix is a geom.Matrix object.
- returns a list of shape objects rotated according to the rotationMatrix.
- """
- # Compute rotation matrix (global --> local --> rotate --> global).
- # rm = self.plane.fG.multiply(matrix).multiply(self.plane.rG)
- # rm = self.computeTransform(rotationMatrix)
-
- # There might be a better way, but to do this rotation takes 3 steps:
- # - transform geometry to local coordinates
- # - then rotate about x
- # - then transform back to global coordinates.
-
- resultWires = []
- for w in listOfShapes:
- mirrored = w.transformGeometry(rotationMatrix.wrapped)
-
- # If the first vertex of the second wire is not coincident with the
- # first or last vertices of the first wire we have to fix the wire
- # so that it will mirror correctly.
- if ((mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[0].X and
- mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[0].Y and
- mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[0].Z) or
- (mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[-1].X and
- mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[-1].Y and
- mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[-1].Z)):
-
- resultWires.append(mirrored)
- else:
- # Make sure that our mirrored edges meet up and are ordered
- # properly.
- aEdges = w.wrapped.Edges
- aEdges.extend(mirrored.wrapped.Edges)
- comp = FreeCADPart.Compound(aEdges)
- mirroredWire = comp.connectEdgesToWires(False).Wires[0]
-
- resultWires.append(cadquery.Shape.cast(mirroredWire))
-
- return resultWires
-
- def _setPlaneDir(self, xDir):
- """Set the vectors parallel to the plane, i.e. xDir and yDir"""
- if (self.zDir.dot(xDir) > 1e-5):
- raise ValueError('xDir must be parralel to the plane')
- xDir = Vector(xDir)
- self.xDir = xDir.normalize()
- self.yDir = self.zDir.cross(self.xDir).normalize()
-
- def _calcTransforms(self):
- """Computes transformation matrices to convert between coordinates
-
- Computes transformation matrices to convert between local and global
- coordinates.
- """
- # r is the forward transformation matrix from world to local coordinates
- # ok i will be really honest, i cannot understand exactly why this works
- # something bout the order of the translation and the rotation.
- # the double-inverting is strange, and I don't understand it.
- r = FreeCAD.Base.Matrix()
-
- # Forward transform must rotate and adjust for origin.
- (r.A11, r.A12, r.A13) = (self.xDir.x, self.xDir.y, self.xDir.z)
- (r.A21, r.A22, r.A23) = (self.yDir.x, self.yDir.y, self.yDir.z)
- (r.A31, r.A32, r.A33) = (self.zDir.x, self.zDir.y, self.zDir.z)
-
- invR = r.inverse()
- invR.A14 = self.origin.x
- invR.A24 = self.origin.y
- invR.A34 = self.origin.z
-
- self.rG = invR
- self.fG = invR.inverse()
-
- def computeTransform(self, tMatrix):
- """Computes the 2-d projection of the supplied matrix"""
-
- return Matrix(self.fG.multiply(tMatrix.wrapped).multiply(self.rG))
-
-
-class BoundBox(object):
- """A BoundingBox for an object or set of objects. Wraps the FreeCAD one"""
- def __init__(self, bb):
- self.wrapped = bb
- self.xmin = bb.XMin
- self.xmax = bb.XMax
- self.xlen = bb.XLength
- self.ymin = bb.YMin
- self.ymax = bb.YMax
- self.ylen = bb.YLength
- self.zmin = bb.ZMin
- self.zmax = bb.ZMax
- self.zlen = bb.ZLength
- self.center = Vector(bb.Center)
- self.DiagonalLength = bb.DiagonalLength
-
- def add(self, obj):
- """Returns a modified (expanded) bounding box
-
- obj can be one of several things:
- 1. a 3-tuple corresponding to x,y, and z amounts to add
- 2. a vector, containing the x,y,z values to add
- 3. another bounding box, where a new box will be created that
- encloses both.
-
- This bounding box is not changed.
- """
- tmp = FreeCAD.Base.BoundBox(self.wrapped)
- if isinstance(obj, tuple):
- tmp.add(obj[0], obj[1], obj[2])
- elif isinstance(obj, Vector):
- tmp.add(obj.fV)
- elif isinstance(obj, BoundBox):
- tmp.add(obj.wrapped)
-
- return BoundBox(tmp)
-
- @classmethod
- def findOutsideBox2D(cls, b1, b2):
- """Compares bounding boxes
-
- Compares bounding boxes. Returns none if neither is inside the other.
- Returns the outer one if either is outside the other.
-
- BoundBox.isInside works in 3d, but this is a 2d bounding box, so it
- doesn't work correctly plus, there was all kinds of rounding error in
- the built-in implementation i do not understand.
- """
- fc_bb1 = b1.wrapped
- fc_bb2 = b2.wrapped
- if (fc_bb1.XMin < fc_bb2.XMin and
- fc_bb1.XMax > fc_bb2.XMax and
- fc_bb1.YMin < fc_bb2.YMin and
- fc_bb1.YMax > fc_bb2.YMax):
- return b1
-
- if (fc_bb2.XMin < fc_bb1.XMin and
- fc_bb2.XMax > fc_bb1.XMax and
- fc_bb2.YMin < fc_bb1.YMin and
- fc_bb2.YMax > fc_bb1.YMax):
- return b2
-
- return None
-
- def isInside(self, anotherBox):
- """Is the provided bounding box inside this one?"""
- return self.wrapped.isInside(anotherBox.wrapped)
diff --git a/CadQuery/Libs/cadquery/freecad_impl/importers.py b/CadQuery/Libs/cadquery/freecad_impl/importers.py
deleted file mode 100644
index 80458be..0000000
--- a/CadQuery/Libs/cadquery/freecad_impl/importers.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
-
- An exporter should provide functionality to accept a shape, and return
- a string containing the model content.
-"""
-import cadquery
-from .shapes import Shape
-
-import FreeCAD
-import Part
-
-class ImportTypes:
- STEP = "STEP"
-
-class UNITS:
- MM = "mm"
- IN = "in"
-
-
-def importShape(importType, fileName):
- """
- Imports a file based on the type (STEP, STL, etc)
- :param importType: The type of file that we're importing
- :param fileName: THe name of the file that we're importing
- """
-
- #Check to see what type of file we're working with
- if importType == ImportTypes.STEP:
- return importStep(fileName)
-
-
-#Loads a STEP file into a CQ.Workplane object
-def importStep(fileName):
- """
- Accepts a file name and loads the STEP file into a cadquery shape
- :param fileName: The path and name of the STEP file to be imported
- """
-
- #Now read and return the shape
- try:
- rshape = Part.read(fileName)
-
- #Make sure that we extract all the solids
- solids = []
- for solid in rshape.Solids:
- solids.append(Shape.cast(solid))
-
- return cadquery.Workplane("XY").newObject(solids)
- except:
- raise ValueError("STEP File Could not be loaded")
diff --git a/CadQuery/Libs/cadquery/freecad_impl/shapes.py b/CadQuery/Libs/cadquery/freecad_impl/shapes.py
deleted file mode 100644
index b870271..0000000
--- a/CadQuery/Libs/cadquery/freecad_impl/shapes.py
+++ /dev/null
@@ -1,908 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
-
- Wrapper Classes for FreeCAD
- These classes provide a stable interface for 3d objects,
- independent of the FreeCAD interface.
-
- Future work might include use of pythonOCC, OCC, or even
- another CAD kernel directly, so this interface layer is quite important.
-
- Funny, in java this is one of those few areas where i'd actually spend the time
- to make an interface and an implementation, but for new these are just rolled together
-
- This interface layer provides three distinct values:
-
- 1. It allows us to avoid changing key api points if we change underlying implementations.
- It would be a disaster if script and plugin authors had to change models because we
- changed implementations
-
- 2. Allow better documentation. One of the reasons FreeCAD is no more popular is because
- its docs are terrible. This allows us to provide good documentation via docstrings
- for each wrapper
-
- 3. Work around bugs. there are a quite a feb bugs in free this layer allows fixing them
-
- 4. allows for enhanced functionality. Many objects are missing features we need. For example
- we need a 'forConstruction' flag on the Wire object. this allows adding those kinds of things
-
- 5. allow changing interfaces when we'd like. there are few cases where the FreeCAD api is not
- very user friendly: we like to change those when necessary. As an example, in the FreeCAD api,
- all factory methods are on the 'Part' object, but it is very useful to know what kind of
- object each one returns, so these are better grouped by the type of object they return.
- (who would know that Part.makeCircle() returns an Edge, but Part.makePolygon() returns a Wire ?
-"""
-from cadquery import Vector, BoundBox
-import FreeCAD
-import Part as FreeCADPart
-
-
-class Shape(object):
- """
- Represents a shape in the system.
- Wrappers the FreeCAD api
- """
-
- def __init__(self, obj):
- self.wrapped = obj
- self.forConstruction = False
-
- @classmethod
- def cast(cls, obj, forConstruction=False):
- "Returns the right type of wrapper, given a FreeCAD object"
- s = obj.ShapeType
- if type(obj) == FreeCAD.Base.Vector:
- return Vector(obj)
- tr = None
-
- # TODO: there is a clever way to do this i'm sure with a lookup
- # but it is not a perfect mapping, because we are trying to hide
- # a bit of the complexity of Compounds in FreeCAD.
- if s == 'Vertex':
- tr = Vertex(obj)
- elif s == 'Edge':
- tr = Edge(obj)
- elif s == 'Wire':
- tr = Wire(obj)
- elif s == 'Face':
- tr = Face(obj)
- elif s == 'Shell':
- tr = Shell(obj)
- elif s == 'Solid':
- tr = Solid(obj)
- elif s == 'Compound':
- #compound of solids, lets return a solid instead
- if len(obj.Solids) > 1:
- tr = Solid(obj)
- elif len(obj.Solids) == 1:
- tr = Solid(obj.Solids[0])
- elif len(obj.Wires) > 0:
- tr = Wire(obj)
- else:
- tr = Compound(obj)
- else:
- raise ValueError("cast:unknown shape type %s" % s)
-
- tr.forConstruction = forConstruction
- return tr
-
- # TODO: all these should move into the exporters folder.
- # we dont need a bunch of exporting code stored in here!
- #
- def exportStl(self, fileName):
- self.wrapped.exportStl(fileName)
-
- def exportStep(self, fileName):
- self.wrapped.exportStep(fileName)
-
- def exportShape(self, fileName, fileFormat):
- if fileFormat == ExportFormats.STL:
- self.wrapped.exportStl(fileName)
- elif fileFormat == ExportFormats.BREP:
- self.wrapped.exportBrep(fileName)
- elif fileFormat == ExportFormats.STEP:
- self.wrapped.exportStep(fileName)
- elif fileFormat == ExportFormats.AMF:
- # not built into FreeCAD
- #TODO: user selected tolerance
- tess = self.wrapped.tessellate(0.1)
- aw = amfUtils.AMFWriter(tess)
- aw.writeAmf(fileName)
- elif fileFormat == ExportFormats.IGES:
- self.wrapped.exportIges(fileName)
- else:
- raise ValueError("Unknown export format: %s" % format)
-
- def geomType(self):
- """
- Gets the underlying geometry type
- :return: a string according to the geometry type.
-
- Implementations can return any values desired, but the
- values the user uses in type filters should correspond to these.
-
- As an example, if a user does::
-
- CQ(object).faces("%mytype")
-
- The expectation is that the geomType attribute will return 'mytype'
-
- The return values depend on the type of the shape:
-
- Vertex: always 'Vertex'
- Edge: LINE, ARC, CIRCLE, SPLINE
- Face: PLANE, SPHERE, CONE
- Solid: 'Solid'
- Shell: 'Shell'
- Compound: 'Compound'
- Wire: 'Wire'
- """
- return self.wrapped.ShapeType
-
- def isType(self, obj, strType):
- """
- Returns True if the shape is the specified type, false otherwise
-
- contrast with ShapeType, which will raise an exception
- if the provide object is not a shape at all
- """
- if hasattr(obj, 'ShapeType'):
- return obj.ShapeType == strType
- else:
- return False
-
- def hashCode(self):
- return self.wrapped.hashCode()
-
- def isNull(self):
- return self.wrapped.isNull()
-
- def isSame(self, other):
- return self.wrapped.isSame(other.wrapped)
-
- def isEqual(self, other):
- return self.wrapped.isEqual(other.wrapped)
-
- def isValid(self):
- return self.wrapped.isValid()
-
- def BoundingBox(self):
- return BoundBox(self.wrapped.BoundBox)
-
- def Center(self):
- # A Part.Shape object doesn't have the CenterOfMass function, but it's wrapped Solid(s) does
- if isinstance(self.wrapped, FreeCADPart.Shape):
- # If there are no Solids, we're probably dealing with a Face or something similar
- if len(self.Solids()) == 0:
- return Vector(self.wrapped.CenterOfMass)
- elif len(self.Solids()) == 1:
- return Vector(self.Solids()[0].wrapped.CenterOfMass)
- elif len(self.Solids()) > 1:
- return self.CombinedCenter(self.Solids())
- elif isinstance(self.wrapped, FreeCADPart.Solid):
- return Vector(self.wrapped.CenterOfMass)
- else:
- raise ValueError("Cannot find the center of %s object type" % str(type(self.Solids()[0].wrapped)))
-
- @staticmethod
- def CombinedCenter(objects):
- """
- Calculates the center of mass of multiple objects.
-
- :param objects: a list of objects with mass
- """
- total_mass = sum(o.wrapped.Mass for o in objects)
- weighted_centers = [o.wrapped.CenterOfMass.multiply(o.wrapped.Mass) for o in objects]
-
- sum_wc = weighted_centers[0]
- for wc in weighted_centers[1:] :
- sum_wc = sum_wc.add(wc)
-
- return Vector(sum_wc.multiply(1./total_mass))
-
- def Closed(self):
- return self.wrapped.Closed
-
- def ShapeType(self):
- return self.wrapped.ShapeType
-
- def Vertices(self):
- return [Vertex(i) for i in self.wrapped.Vertexes]
-
- def Edges(self):
- return [Edge(i) for i in self.wrapped.Edges]
-
- def Compounds(self):
- return [Compound(i) for i in self.wrapped.Compounds]
-
- def Wires(self):
- return [Wire(i) for i in self.wrapped.Wires]
-
- def Faces(self):
- return [Face(i) for i in self.wrapped.Faces]
-
- def Shells(self):
- return [Shell(i) for i in self.wrapped.Shells]
-
- def Solids(self):
- return [Solid(i) for i in self.wrapped.Solids]
-
- def Area(self):
- return self.wrapped.Area
-
- def Length(self):
- return self.wrapped.Length
-
- def rotate(self, startVector, endVector, angleDegrees):
- """
- Rotates a shape around an axis
- :param startVector: start point of rotation axis either a 3-tuple or a Vector
- :param endVector: end point of rotation axis, either a 3-tuple or a Vector
- :param angleDegrees: angle to rotate, in degrees
- :return: a copy of the shape, rotated
- """
- if type(startVector) == tuple:
- startVector = Vector(startVector)
-
- if type(endVector) == tuple:
- endVector = Vector(endVector)
-
- tmp = self.wrapped.copy()
- tmp.rotate(startVector.wrapped, endVector.wrapped, angleDegrees)
- return Shape.cast(tmp)
-
- def translate(self, vector):
-
- if type(vector) == tuple:
- vector = Vector(vector)
- tmp = self.wrapped.copy()
- tmp.translate(vector.wrapped)
- return Shape.cast(tmp)
-
- def scale(self, factor):
- tmp = self.wrapped.copy()
- tmp.scale(factor)
- return Shape.cast(tmp)
-
- def copy(self):
- return Shape.cast(self.wrapped.copy())
-
- def transformShape(self, tMatrix):
- """
- tMatrix is a matrix object.
- returns a copy of the ojbect, transformed by the provided matrix,
- with all objects keeping their type
- """
- tmp = self.wrapped.copy()
- tmp.transformShape(tMatrix)
- r = Shape.cast(tmp)
- r.forConstruction = self.forConstruction
- return r
-
- def transformGeometry(self, tMatrix):
- """
- tMatrix is a matrix object.
-
- returns a copy of the object, but with geometry transformed insetad of just
- rotated.
-
- WARNING: transformGeometry will sometimes convert lines and circles to splines,
- but it also has the ability to handle skew and stretching transformations.
-
- If your transformation is only translation and rotation, it is safer to use transformShape,
- which doesnt change the underlying type of the geometry, but cannot handle skew transformations
- """
- tmp = self.wrapped.copy()
- tmp = tmp.transformGeometry(tMatrix)
- return Shape.cast(tmp)
-
- def __hash__(self):
- return self.wrapped.hashCode()
-
-
-class Vertex(Shape):
- def __init__(self, obj, forConstruction=False):
- """
- Create a vertex from a FreeCAD Vertex
- """
- self.wrapped = obj
- self.forConstruction = forConstruction
- self.X = obj.X
- self.Y = obj.Y
- self.Z = obj.Z
-
- def toTuple(self):
- return (self.X, self.Y, self.Z)
-
- def Center(self):
- """
- The center of a vertex is itself!
- """
- return Vector(self.wrapped.Point)
-
-
-class Edge(Shape):
- def __init__(self, obj):
- """
- An Edge
- """
- self.wrapped = obj
- # self.startPoint = None
- # self.endPoint = None
-
- self.edgetypes = {
- FreeCADPart.Line: 'LINE',
- FreeCADPart.ArcOfCircle: 'ARC',
- FreeCADPart.Circle: 'CIRCLE'
- }
-
- def geomType(self):
- t = type(self.wrapped.Curve)
- if self.edgetypes.has_key(t):
- return self.edgetypes[t]
- else:
- return "Unknown Edge Curve Type: %s" % str(t)
-
- def startPoint(self):
- """
-
- :return: a vector representing the start poing of this edge
-
- Note, circles may have the start and end points the same
- """
- # work around freecad bug where valueAt is unreliable
- curve = self.wrapped.Curve
- return Vector(curve.value(self.wrapped.ParameterRange[0]))
-
- def endPoint(self):
- """
-
- :return: a vector representing the end point of this edge.
-
- Note, circles may have the start and end points the same
-
- """
- # warning: easier syntax in freecad of .valueAt(.ParameterRange[1]) has
- # a bug with curves other than arcs, but using the underlying curve directly seems to work
- # that's the solution i'm using below
- curve = self.wrapped.Curve
- v = Vector(curve.value(self.wrapped.ParameterRange[1]))
- return v
-
- def tangentAt(self, locationVector=None):
- """
- Compute tangent vector at the specified location.
- :param locationVector: location to use. Use the center point if None
- :return: tangent vector
- """
- if locationVector is None:
- locationVector = self.Center()
-
- p = self.wrapped.Curve.parameter(locationVector.wrapped)
- return Vector(self.wrapped.tangentAt(p))
-
- @classmethod
- def makeCircle(cls, radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=360.0, angle2=360):
- return Edge(FreeCADPart.makeCircle(radius, toVector(pnt), toVector(dir), angle1, angle2))
-
- @classmethod
- def makeSpline(cls, listOfVector):
- """
- Interpolate a spline through the provided points.
- :param cls:
- :param listOfVector: a list of Vectors that represent the points
- :return: an Edge
- """
- vecs = [v.wrapped for v in listOfVector]
-
- spline = FreeCADPart.BSplineCurve()
- spline.interpolate(vecs, False)
- return Edge(spline.toShape())
-
- @classmethod
- def makeThreePointArc(cls, v1, v2, v3):
- """
- Makes a three point arc through the provided points
- :param cls:
- :param v1: start vector
- :param v2: middle vector
- :param v3: end vector
- :return: an edge object through the three points
- """
- arc = FreeCADPart.Arc(v1.wrapped, v2.wrapped, v3.wrapped)
- e = Edge(arc.toShape())
- return e # arcane and undocumented, this creates an Edge object
-
- @classmethod
- def makeLine(cls, v1, v2):
- """
- Create a line between two points
- :param v1: Vector that represents the first point
- :param v2: Vector that represents the second point
- :return: A linear edge between the two provided points
- """
- return Edge(FreeCADPart.makeLine(v1.toTuple(), v2.toTuple()))
-
-
-class Wire(Shape):
- def __init__(self, obj):
- """
- A Wire
- """
- self.wrapped = obj
-
- @classmethod
- def combine(cls, listOfWires):
- """
- Attempt to combine a list of wires into a new wire.
- the wires are returned in a list.
- :param cls:
- :param listOfWires:
- :return:
- """
- return Shape.cast(FreeCADPart.Wire([w.wrapped for w in listOfWires]))
-
- @classmethod
- def assembleEdges(cls, listOfEdges):
- """
- Attempts to build a wire that consists of the edges in the provided list
- :param cls:
- :param listOfEdges: a list of Edge objects
- :return: a wire with the edges assembled
- """
- fCEdges = [a.wrapped for a in listOfEdges]
-
- wa = Wire(FreeCADPart.Wire(fCEdges))
- return wa
-
- @classmethod
- def makeCircle(cls, radius, center, normal):
- """
- Makes a Circle centered at the provided point, having normal in the provided direction
- :param radius: floating point radius of the circle, must be > 0
- :param center: vector representing the center of the circle
- :param normal: vector representing the direction of the plane the circle should lie in
- :return:
- """
- w = Wire(FreeCADPart.Wire([FreeCADPart.makeCircle(radius, center.wrapped, normal.wrapped)]))
- return w
-
- @classmethod
- def makePolygon(cls, listOfVertices, forConstruction=False):
- # convert list of tuples into Vectors.
- w = Wire(FreeCADPart.makePolygon([i.wrapped for i in listOfVertices]))
- w.forConstruction = forConstruction
- return w
-
- @classmethod
- def makeHelix(cls, pitch, height, radius, angle=360.0):
- """
- Make a helix with a given pitch, height and radius
- By default a cylindrical surface is used to create the helix. If
- the fourth parameter is set (the apex given in degree) a conical surface is used instead'
- """
- return Wire(FreeCADPart.makeHelix(pitch, height, radius, angle))
-
- def clean(self):
- """This method is not implemented yet."""
- return self
-
-class Face(Shape):
- def __init__(self, obj):
- """
- A Face
- """
- self.wrapped = obj
-
- self.facetypes = {
- # TODO: bezier,bspline etc
- FreeCADPart.Plane: 'PLANE',
- FreeCADPart.Sphere: 'SPHERE',
- FreeCADPart.Cone: 'CONE'
- }
-
- def geomType(self):
- t = type(self.wrapped.Surface)
- if self.facetypes.has_key(t):
- return self.facetypes[t]
- else:
- return "Unknown Face Surface Type: %s" % str(t)
-
- def normalAt(self, locationVector=None):
- """
- Computes the normal vector at the desired location on the face.
-
- :returns: a vector representing the direction
- :param locationVector: the location to compute the normal at. If none, the center of the face is used.
- :type locationVector: a vector that lies on the surface.
- """
- if locationVector == None:
- locationVector = self.Center()
- (u, v) = self.wrapped.Surface.parameter(locationVector.wrapped)
-
- return Vector(self.wrapped.normalAt(u, v).normalize())
-
- @classmethod
- def makePlane(cls, length, width, basePnt=None, dir=None):
- return Face(FreeCADPart.makePlan(length, width, toVector(basePnt), toVector(dir)))
-
- @classmethod
- def makeRuledSurface(cls, edgeOrWire1, edgeOrWire2, dist=None):
- """
- 'makeRuledSurface(Edge|Wire,Edge|Wire) -- Make a ruled surface
- Create a ruled surface out of two edges or wires. If wires are used then
- these must have the same
- """
- return Shape.cast(FreeCADPart.makeRuledSurface(edgeOrWire1.obj, edgeOrWire2.obj, dist))
-
- def cut(self, faceToCut):
- "Remove a face from another one"
- return Shape.cast(self.obj.cut(faceToCut.obj))
-
- def fuse(self, faceToJoin):
- return Shape.cast(self.obj.fuse(faceToJoin.obj))
-
- def intersect(self, faceToIntersect):
- """
- computes the intersection between the face and the supplied one.
- The result could be a face or a compound of faces
- """
- return Shape.cast(self.obj.common(faceToIntersect.obj))
-
-
-class Shell(Shape):
- def __init__(self, wrapped):
- """
- A Shell
- """
- self.wrapped = wrapped
-
- @classmethod
- def makeShell(cls, listOfFaces):
- return Shell(FreeCADPart.makeShell([i.obj for i in listOfFaces]))
-
-
-class Solid(Shape):
- def __init__(self, obj):
- """
- A Solid
- """
- self.wrapped = obj
-
- @classmethod
- def isSolid(cls, obj):
- """
- Returns true if the object is a FreeCAD solid, false otherwise
- """
- if hasattr(obj, 'ShapeType'):
- if obj.ShapeType == 'Solid' or \
- (obj.ShapeType == 'Compound' and len(obj.Solids) > 0):
- return True
- return False
-
- @classmethod
- def makeBox(cls, length, width, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1)):
- """
- makeBox(length,width,height,[pnt,dir]) -- Make a box located\nin pnt with the d
- imensions (length,width,height)\nBy default pnt=Vector(0,0,0) and dir=Vector(0,0,1)'
- """
- return Shape.cast(FreeCADPart.makeBox(length, width, height, pnt.wrapped, dir.wrapped))
-
- @classmethod
- def makeCone(cls, radius1, radius2, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360):
- """
- 'makeCone(radius1,radius2,height,[pnt,dir,angle]) --
- Make a cone with given radii and height\nBy default pnt=Vector(0,0,0),
- dir=Vector(0,0,1) and angle=360'
- """
- return Shape.cast(FreeCADPart.makeCone(radius1, radius2, height, pnt.wrapped, dir.wrapped, angleDegrees))
-
- @classmethod
- def makeCylinder(cls, radius, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360):
- """
- makeCylinder(radius,height,[pnt,dir,angle]) --
- Make a cylinder with a given radius and height
- By default pnt=Vector(0,0,0),dir=Vector(0,0,1) and angle=360'
- """
- return Shape.cast(FreeCADPart.makeCylinder(radius, height, pnt.wrapped, dir.wrapped, angleDegrees))
-
- @classmethod
- def makeTorus(cls, radius1, radius2, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None):
- """
- makeTorus(radius1,radius2,[pnt,dir,angle1,angle2,angle]) --
- Make a torus with agiven radii and angles
- By default pnt=Vector(0,0,0),dir=Vector(0,0,1),angle1=0
- ,angle1=360 and angle=360'
- """
- return Shape.cast(FreeCADPart.makeTorus(radius1, radius2, pnt, dir, angleDegrees1, angleDegrees2))
-
- @classmethod
- def sweep(cls, profileWire, pathWire):
- """
- make a solid by sweeping the profileWire along the specified path
- :param cls:
- :param profileWire:
- :param pathWire:
- :return:
- """
- # needs to use freecad wire.makePipe or makePipeShell
- # needs to allow free-space wires ( those not made from a workplane )
-
- @classmethod
- def makeLoft(cls, listOfWire, ruled=False):
- """
- makes a loft from a list of wires
- The wires will be converted into faces when possible-- it is presumed that nobody ever actually
- wants to make an infinitely thin shell for a real FreeCADPart.
- """
- # the True flag requests building a solid instead of a shell.
-
- return Shape.cast(FreeCADPart.makeLoft([i.wrapped for i in listOfWire], True, ruled))
-
- @classmethod
- def makeWedge(cls, xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt=None, dir=None):
- """
- 'makeWedge(xmin, ymin, zmin, z2min, x2min,
- xmax, ymax, zmax, z2max, x2max,[pnt, dir])
- Make a wedge located in pnt\nBy default pnt=Vector(0,0,0) and dir=Vec
- tor(0,0,1)'
- """
- return Shape.cast(
- FreeCADPart.makeWedge(xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt, dir))
-
- @classmethod
- def makeSphere(cls, radius, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None):
- """
- 'makeSphere(radius,[pnt, dir, angle1,angle2,angle3]) --
- Make a sphere with a giv
- en radius\nBy default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360'
- """
- return Shape.cast(FreeCADPart.makeSphere(radius, pnt.wrapped, dir.wrapped, angleDegrees1, angleDegrees2, angleDegrees3))
-
- @classmethod
- def extrudeLinearWithRotation(cls, outerWire, innerWires, vecCenter, vecNormal, angleDegrees):
- """
- Creates a 'twisted prism' by extruding, while simultaneously rotating around the extrusion vector.
-
- Though the signature may appear to be similar enough to extrudeLinear to merit combining them, the
- construction methods used here are different enough that they should be separate.
-
- At a high level, the steps followed ar:
- (1) accept a set of wires
- (2) create another set of wires like this one, but which are transformed and rotated
- (3) create a ruledSurface between the sets of wires
- (40 create a shell and compute the resulting object
-
- :param outerWire: the outermost wire, a cad.Wire
- :param innerWires: a list of inner wires, a list of cad.Wire
- :param vecCenter: the center point about which to rotate. the axis of rotation is defined by
- vecNormal, located at vecCenter. ( a cad.Vector )
- :param vecNormal: a vector along which to extrude the wires ( a cad.Vector )
- :param angleDegrees: the angle to rotate through while extruding
- :return: a cad.Solid object
- """
-
- # from this point down we are dealing with FreeCAD wires not cad.wires
- startWires = [outerWire.wrapped] + [i.wrapped for i in innerWires]
- endWires = []
- p1 = vecCenter.wrapped
- p2 = vecCenter.add(vecNormal).wrapped
-
- # make translated and rotated copy of each wire
- for w in startWires:
- w2 = w.copy()
- w2.translate(vecNormal.wrapped)
- w2.rotate(p1, p2, angleDegrees)
- endWires.append(w2)
-
- # make a ruled surface for each set of wires
- sides = []
- for w1, w2 in zip(startWires, endWires):
- rs = FreeCADPart.makeRuledSurface(w1, w2)
- sides.append(rs)
-
- #make faces for the top and bottom
- startFace = FreeCADPart.Face(startWires)
- endFace = FreeCADPart.Face(endWires)
-
- #collect all the faces from the sides
- faceList = [startFace]
- for s in sides:
- faceList.extend(s.Faces)
- faceList.append(endFace)
-
- shell = FreeCADPart.makeShell(faceList)
- solid = FreeCADPart.makeSolid(shell)
- return Shape.cast(solid)
-
- @classmethod
- def extrudeLinear(cls, outerWire, innerWires, vecNormal):
- """
- Attempt to extrude the list of wires into a prismatic solid in the provided direction
-
- :param outerWire: the outermost wire
- :param innerWires: a list of inner wires
- :param vecNormal: a vector along which to extrude the wires
- :return: a Solid object
-
- The wires must not intersect
-
- Extruding wires is very non-trivial. Nested wires imply very different geometry, and
- there are many geometries that are invalid. In general, the following conditions must be met:
-
- * all wires must be closed
- * there cannot be any intersecting or self-intersecting wires
- * wires must be listed from outside in
- * more than one levels of nesting is not supported reliably
-
- This method will attempt to sort the wires, but there is much work remaining to make this method
- reliable.
- """
-
- # one would think that fusing faces into a compound and then extruding would work,
- # but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc),
- # but then cutting it from the main solid fails with BRep_NotDone.
- #the work around is to extrude each and then join the resulting solids, which seems to work
-
- #FreeCAD allows this in one operation, but others might not
- freeCADWires = [outerWire.wrapped]
- for w in innerWires:
- freeCADWires.append(w.wrapped)
-
- f = FreeCADPart.Face(freeCADWires)
- result = f.extrude(vecNormal.wrapped)
-
- return Shape.cast(result)
-
- @classmethod
- def revolve(cls, outerWire, innerWires, angleDegrees, axisStart, axisEnd):
- """
- Attempt to revolve the list of wires into a solid in the provided direction
-
- :param outerWire: the outermost wire
- :param innerWires: a list of inner wires
- :param angleDegrees: the angle to revolve through.
- :type angleDegrees: float, anything less than 360 degrees will leave the shape open
- :param axisStart: the start point of the axis of rotation
- :type axisStart: tuple, a two tuple
- :param axisEnd: the end point of the axis of rotation
- :type axisEnd: tuple, a two tuple
- :return: a Solid object
-
- The wires must not intersect
-
- * all wires must be closed
- * there cannot be any intersecting or self-intersecting wires
- * wires must be listed from outside in
- * more than one levels of nesting is not supported reliably
- * the wire(s) that you're revolving cannot be centered
-
- This method will attempt to sort the wires, but there is much work remaining to make this method
- reliable.
- """
- freeCADWires = [outerWire.wrapped]
-
- for w in innerWires:
- freeCADWires.append(w.wrapped)
-
- f = FreeCADPart.Face(freeCADWires)
-
- rotateCenter = FreeCAD.Base.Vector(axisStart)
- rotateAxis = FreeCAD.Base.Vector(axisEnd)
-
- #Convert our axis end vector into to something FreeCAD will understand (an axis specification vector)
- rotateAxis = rotateCenter.sub(rotateAxis)
-
- #FreeCAD wants a rotation center and then an axis to rotate around rather than an axis of rotation
- result = f.revolve(rotateCenter, rotateAxis, angleDegrees)
-
- return Shape.cast(result)
-
- def tessellate(self, tolerance):
- return self.wrapped.tessellate(tolerance)
-
- def intersect(self, toIntersect):
- """
- computes the intersection between this solid and the supplied one
- The result could be a face or a compound of faces
- """
- return Shape.cast(self.wrapped.common(toIntersect.wrapped))
-
- def cut(self, solidToCut):
- "Remove a solid from another one"
- return Shape.cast(self.wrapped.cut(solidToCut.wrapped))
-
- def fuse(self, solidToJoin):
- return Shape.cast(self.wrapped.fuse(solidToJoin.wrapped))
-
- def clean(self):
- """Clean faces by removing splitter edges."""
- r = self.wrapped.removeSplitter()
- # removeSplitter() returns a generic Shape type, cast to actual type of object
- r = FreeCADPart.cast_to_shape(r)
- return Shape.cast(r)
-
- def fillet(self, radius, edgeList):
- """
- Fillets the specified edges of this solid.
- :param radius: float > 0, the radius of the fillet
- :param edgeList: a list of Edge objects, which must belong to this solid
- :return: Filleted solid
- """
- nativeEdges = [e.wrapped for e in edgeList]
- return Shape.cast(self.wrapped.makeFillet(radius, nativeEdges))
-
- def chamfer(self, length, length2, edgeList):
- """
- Chamfers the specified edges of this solid.
- :param length: length > 0, the length (length) of the chamfer
- :param length2: length2 > 0, optional parameter for asymmetrical chamfer. Should be `None` if not required.
- :param edgeList: a list of Edge objects, which must belong to this solid
- :return: Chamfered solid
- """
- nativeEdges = [e.wrapped for e in edgeList]
- # note: we prefer 'length' word to 'radius' as opposed to FreeCAD's API
- if length2:
- return Shape.cast(self.wrapped.makeChamfer(length, length2, nativeEdges))
- else:
- return Shape.cast(self.wrapped.makeChamfer(length, nativeEdges))
-
- def shell(self, faceList, thickness, tolerance=0.0001):
- """
- make a shelled solid of given by removing the list of faces
-
- :param faceList: list of face objects, which must be part of the solid.
- :param thickness: floating point thickness. positive shells outwards, negative shells inwards
- :param tolerance: modelling tolerance of the method, default=0.0001
- :return: a shelled solid
-
- **WARNING** The underlying FreeCAD implementation can very frequently have problems
- with shelling complex geometries!
- """
- nativeFaces = [f.wrapped for f in faceList]
- return Shape.cast(self.wrapped.makeThickness(nativeFaces, thickness, tolerance))
-
-
-class Compound(Shape):
- def __init__(self, obj):
- """
- An Edge
- """
- self.wrapped = obj
-
- def Center(self):
- return self.Center()
-
- @classmethod
- def makeCompound(cls, listOfShapes):
- """
- Create a compound out of a list of shapes
- """
- solids = [s.wrapped for s in listOfShapes]
- c = FreeCADPart.Compound(solids)
- return Shape.cast(c)
-
- def fuse(self, toJoin):
- return Shape.cast(self.wrapped.fuse(toJoin.wrapped))
-
- def tessellate(self, tolerance):
- return self.wrapped.tessellate(tolerance)
-
- def clean(self):
- """This method is not implemented yet."""
- return self
diff --git a/CadQuery/Libs/cadquery/plugins/__init__.py b/CadQuery/Libs/cadquery/plugins/__init__.py
deleted file mode 100644
index 3697b9f..0000000
--- a/CadQuery/Libs/cadquery/plugins/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
- CadQuery
- Copyright (C) 2015 Parametric Products Intellectual Holdings, LLC
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-"""
diff --git a/CadQuery/Libs/cadquery/selectors.py b/CadQuery/Libs/cadquery/selectors.py
deleted file mode 100644
index be07d7b..0000000
--- a/CadQuery/Libs/cadquery/selectors.py
+++ /dev/null
@@ -1,474 +0,0 @@
-"""
- Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
-
- This file is part of CadQuery.
-
- CadQuery is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- CadQuery is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; If not, see
-"""
-
-import re
-import math
-from cadquery import Vector,Edge,Vertex,Face,Solid,Shell,Compound
-
-
-class Selector(object):
- """
- Filters a list of objects
-
- Filters must provide a single method that filters objects.
- """
- def filter(self,objectList):
- """
- Filter the provided list
- :param objectList: list to filter
- :type objectList: list of FreeCAD primatives
- :return: filtered list
-
- The default implementation returns the original list unfiltered
-
- """
- return objectList
-
- def __and__(self, other):
- return AndSelector(self, other)
-
- def __add__(self, other):
- return SumSelector(self, other)
-
- def __sub__(self, other):
- return SubtractSelector(self, other)
-
- def __neg__(self):
- return InverseSelector(self)
-
-class NearestToPointSelector(Selector):
- """
- Selects object nearest the provided point.
-
- If the object is a vertex or point, the distance
- is used. For other kinds of shapes, the center of mass
- is used to to compute which is closest.
-
- Applicability: All Types of Shapes
-
- Example::
-
- CQ(aCube).vertices(NearestToPointSelector((0,1,0))
-
- returns the vertex of the unit cube closest to the point x=0,y=1,z=0
-
- """
- def __init__(self,pnt ):
- self.pnt = pnt
- def filter(self,objectList):
-
- def dist(tShape):
- return tShape.Center().sub(Vector(*self.pnt)).Length
- #if tShape.ShapeType == 'Vertex':
- # return tShape.Point.sub(toVector(self.pnt)).Length
- #else:
- # return tShape.CenterOfMass.sub(toVector(self.pnt)).Length
-
- return [ min(objectList,key=dist) ]
-
-class BoxSelector(Selector):
- """
- Selects objects inside the 3D box defined by 2 points.
-
- If `boundingbox` is True only the objects that have their bounding
- box inside the given box is selected. Otherwise only center point
- of the object is tested.
-
- Applicability: all types of shapes
-
- Example::
-
- CQ(aCube).edges(BoxSelector((0,1,0), (1,2,1))
- """
- def __init__(self, point0, point1, boundingbox=False):
- self.p0 = Vector(*point0)
- self.p1 = Vector(*point1)
- self.test_boundingbox = boundingbox
-
- def filter(self, objectList):
-
- result = []
- x0, y0, z0 = self.p0.toTuple()
- x1, y1, z1 = self.p1.toTuple()
-
- def isInsideBox(p):
- # using XOR for checking if x/y/z is in between regardless
- # of order of x/y/z0 and x/y/z1
- return ((p.x < x0) ^ (p.x < x1)) and \
- ((p.y < y0) ^ (p.y < y1)) and \
- ((p.z < z0) ^ (p.z < z1))
-
- for o in objectList:
- if self.test_boundingbox:
- bb = o.BoundingBox()
- if isInsideBox(Vector(bb.xmin, bb.ymin, bb.zmin)) and \
- isInsideBox(Vector(bb.xmax, bb.ymax, bb.zmax)):
- result.append(o)
- else:
- if isInsideBox(o.Center()):
- result.append(o)
-
- return result
-
-class BaseDirSelector(Selector):
- """
- A selector that handles selection on the basis of a single
- direction vector
- """
- def __init__(self,vector,tolerance=0.0001 ):
- self.direction = vector
- self.TOLERANCE = tolerance
-
- def test(self,vec):
- "Test a specified vector. Subclasses override to provide other implementations"
- return True
-
- def filter(self,objectList):
- """
- There are lots of kinds of filters, but
- for planes they are always based on the normal of the plane,
- and for edges on the tangent vector along the edge
- """
- r = []
- for o in objectList:
- #no really good way to avoid a switch here, edges and faces are simply different!
-
- if type(o) == Face:
- # a face is only parallell to a direction if it is a plane, and its normal is parallel to the dir
- normal = o.normalAt(None)
-
- if self.test(normal):
- r.append(o)
- elif type(o) == Edge and o.geomType() == 'LINE':
- #an edge is parallel to a direction if it is a line, and the line is parallel to the dir
- tangent = o.tangentAt(None)
- if self.test(tangent):
- r.append(o)
-
- return r
-
-class ParallelDirSelector(BaseDirSelector):
- """
- Selects objects parallel with the provided direction
-
- Applicability:
- Linear Edges
- Planar Faces
-
- Use the string syntax shortcut \|(X|Y|Z) if you want to select
- based on a cardinal direction.
-
- Example::
-
- CQ(aCube).faces(ParallelDirSelector((0,0,1))
-
- selects faces with a normals in the z direction, and is equivalent to::
-
- CQ(aCube).faces("|Z")
- """
-
- def test(self,vec):
- return self.direction.cross(vec).Length < self.TOLERANCE
-
-class DirectionSelector(BaseDirSelector):
- """
- Selects objects aligned with the provided direction
-
- Applicability:
- Linear Edges
- Planar Faces
-
- Use the string syntax shortcut +/-(X|Y|Z) if you want to select
- based on a cardinal direction.
-
- Example::
-
- CQ(aCube).faces(DirectionSelector((0,0,1))
-
- selects faces with a normals in the z direction, and is equivalent to::
-
- CQ(aCube).faces("+Z")
- """
-
- def test(self,vec):
- return abs(self.direction.getAngle(vec) < self.TOLERANCE)
-
-class PerpendicularDirSelector(BaseDirSelector):
- """
- Selects objects perpendicular with the provided direction
-
- Applicability:
- Linear Edges
- Planar Faces
-
- Use the string syntax shortcut #(X|Y|Z) if you want to select
- based on a cardinal direction.
-
- Example::
-
- CQ(aCube).faces(PerpendicularDirSelector((0,0,1))
-
- selects faces with a normals perpendicular to the z direction, and is equivalent to::
-
- CQ(aCube).faces("#Z")
- """
-
- def test(self,vec):
- angle = self.direction.getAngle(vec)
- r = (abs(angle) < self.TOLERANCE) or (abs(angle - math.pi) < self.TOLERANCE )
- return not r
-
-
-class TypeSelector(Selector):
- """
- Selects objects of the prescribed topological type.
-
- Applicability:
- Faces: Plane,Cylinder,Sphere
- Edges: Line,Circle,Arc
-
- You can use the shortcut selector %(PLANE|SPHERE|CONE) for faces,
- and %(LINE|ARC|CIRCLE) for edges.
-
- For example this::
-
- CQ(aCube).faces ( TypeSelector("PLANE") )
-
- will select 6 faces, and is equivalent to::
-
- CQ(aCube).faces( "%PLANE" )
-
- """
- def __init__(self,typeString):
- self.typeString = typeString.upper()
-
- def filter(self,objectList):
- r = []
- for o in objectList:
- if o.geomType() == self.typeString:
- r.append(o)
- return r
-
-class DirectionMinMaxSelector(Selector):
- """
- Selects objects closest or farthest in the specified direction
- Used for faces, points, and edges
-
- Applicability:
- All object types. for a vertex, its point is used. for all other kinds
- of objects, the center of mass of the object is used.
-
- You can use the string shortcuts >(X|Y|Z) or <(X|Y|Z) if you want to
- select based on a cardinal direction.
-
- For example this::
-
- CQ(aCube).faces ( DirectionMinMaxSelector((0,0,1),True )
-
- Means to select the face having the center of mass farthest in the positive z direction,
- and is the same as:
-
- CQ(aCube).faces( ">Z" )
-
- Future Enhancements:
- provide a nicer way to select in arbitrary directions. IE, a bit more code could
- allow '>(0,0,1)' to work.
-
- """
- def __init__(self, vector, directionMax=True, tolerance=0.0001):
- self.vector = vector
- self.max = max
- self.directionMax = directionMax
- self.TOLERANCE = tolerance
- def filter(self,objectList):
-
- def distance(tShape):
- return tShape.Center().dot(self.vector)
- #if tShape.ShapeType == 'Vertex':
- # pnt = tShape.Point
- #else:
- # pnt = tShape.Center()
- #return pnt.dot(self.vector)
-
- # find out the max/min distance
- if self.directionMax:
- d = max(map(distance, objectList))
- else:
- d = min(map(distance, objectList))
-
- # return all objects at the max/min distance (within a tolerance)
- return filter(lambda o: abs(d - distance(o)) < self.TOLERANCE, objectList)
-
-class BinarySelector(Selector):
- """
- Base class for selectors that operates with two other
- selectors. Subclass must implement the :filterResults(): method.
- """
- def __init__(self, left, right):
- self.left = left
- self.right = right
-
- def filter(self, objectList):
- return self.filterResults(self.left.filter(objectList),
- self.right.filter(objectList))
-
- def filterResults(self, r_left, r_right):
- raise NotImplementedError
-
-class AndSelector(BinarySelector):
- """
- Intersection selector. Returns objects that is selected by both selectors.
- """
- def filterResults(self, r_left, r_right):
- # return intersection of lists
- return list(set(r_left) & set(r_right))
-
-class SumSelector(BinarySelector):
- """
- Union selector. Returns the sum of two selectors results.
- """
- def filterResults(self, r_left, r_right):
- # return the union (no duplicates) of lists
- return list(set(r_left + r_right))
-
-class SubtractSelector(BinarySelector):
- """
- Difference selector. Substract results of a selector from another
- selectors results.
- """
- def filterResults(self, r_left, r_right):
- return list(set(r_left) - set(r_right))
-
-class InverseSelector(Selector):
- """
- Inverts the selection of given selector. In other words, selects
- all objects that is not selected by given selector.
- """
- def __init__(self, selector):
- self.selector = selector
-
- def filter(self, objectList):
- # note that Selector() selects everything
- return SubtractSelector(Selector(), self.selector).filter(objectList)
-
-class StringSyntaxSelector(Selector):
- """
- Filter lists objects using a simple string syntax. All of the filters available in the string syntax
- are also available ( usually with more functionality ) through the creation of full-fledged
- selector objects. see :py:class:`Selector` and its subclasses
-
- Filtering works differently depending on the type of object list being filtered.
-
- :param selectorString: A two-part selector string, [selector][axis]
-
- :return: objects that match the specified selector
-
- ***Modfiers*** are ``('|','+','-','<','>','%')``
-
- :\|:
- parallel to ( same as :py:class:`ParallelDirSelector` ). Can return multiple objects.
- :#:
- perpendicular to (same as :py:class:`PerpendicularDirSelector` )
- :+:
- positive direction (same as :py:class:`DirectionSelector` )
- :-:
- negative direction (same as :py:class:`DirectionSelector` )
- :>:
- maximize (same as :py:class:`DirectionMinMaxSelector` with directionMax=True)
- :<:
- minimize (same as :py:class:`DirectionMinMaxSelector` with directionMax=False )
- :%:
- curve/surface type (same as :py:class:`TypeSelector`)
-
- ***axisStrings*** are: ``X,Y,Z,XY,YZ,XZ``
-
- Selectors are a complex topic: see :ref:`selector_reference` for more information
-
-
-
- """
- def __init__(self,selectorString):
-
- self.axes = {
- 'X': Vector(1,0,0),
- 'Y': Vector(0,1,0),
- 'Z': Vector(0,0,1),
- 'XY': Vector(1,1,0),
- 'YZ': Vector(0,1,1),
- 'XZ': Vector(1,0,1)
- }
-
- namedViews = {
- 'front': ('>','Z' ),
- 'back': ('<','Z'),
- 'left':('<', 'X'),
- 'right': ('>', 'X'),
- 'top': ('>','Y'),
- 'bottom': ('<','Y')
- }
- self.selectorString = selectorString
- r = re.compile("\s*([-\+<>\|\%#])*\s*(\w+)\s*",re.IGNORECASE)
- m = r.match(selectorString)
-
- if m != None:
- if namedViews.has_key(selectorString):
- (a,b) = namedViews[selectorString]
- self.mySelector = self._chooseSelector(a,b )
- else:
- self.mySelector = self._chooseSelector(m.groups()[0],m.groups()[1])
- else:
- raise ValueError ("Selector String format must be [-+<>|#%] X|Y|Z ")
-
-
- def _chooseSelector(self,selType,selAxis):
- """Sets up the underlying filters accordingly"""
-
- if selType == "%":
- return TypeSelector(selAxis)
-
- #all other types need to select axis as a vector
- #get the axis vector first, will throw an except if an unknown axis is used
- try:
- vec = self.axes[selAxis]
- except KeyError:
- raise ValueError ("Axis value %s not allowed: must be one of %s" % (selAxis, str(self.axes)))
-
- if selType in (None, "+"):
- #use direction filter
- return DirectionSelector(vec)
- elif selType == '-':
- #just use the reverse of the direction vector
- return DirectionSelector(vec.multiply(-1.0))
- elif selType == "|":
- return ParallelDirSelector(vec)
- elif selType == ">":
- return DirectionMinMaxSelector(vec,True)
- elif selType == "<":
- return DirectionMinMaxSelector(vec,False)
- elif selType == '#':
- return PerpendicularDirSelector(vec)
- else:
- raise ValueError ("Selector String format must be [-+<>|] X|Y|Z ")
-
- def filter(self,objectList):
- """
- selects minimum, maximum, positive or negative values relative to a direction
- [+\|-\|<\|>\|] \
- """
- return self.mySelector.filter(objectList)