From e7d8a92e0a43f07d8e65f6e3452bc7f407058917 Mon Sep 17 00:00:00 2001 From: Dave Cowden Date: Sun, 13 Dec 2015 08:33:12 -0500 Subject: [PATCH] removed extraneous build directory --- build/lib.linux-i686-2.7/cadquery/__init__.py | 21 - .../cadquery/contrib/__init__.py | 18 - build/lib.linux-i686-2.7/cadquery/cq.py | 2467 ----------------- .../cadquery/cq_directive.py | 85 - build/lib.linux-i686-2.7/cadquery/cqgi.py | 425 --- .../cadquery/freecad_impl/__init__.py | 112 - .../cadquery/freecad_impl/exporters.py | 392 --- .../cadquery/freecad_impl/geom.py | 647 ----- .../cadquery/freecad_impl/importers.py | 71 - .../cadquery/freecad_impl/shapes.py | 982 ------- .../cadquery/plugins/__init__.py | 18 - .../lib.linux-i686-2.7/cadquery/selectors.py | 474 ---- build/lib.linux-i686-2.7/tests/TestCQGI.py | 170 -- .../tests/TestCQSelectors.py | 358 --- .../tests/TestCadObjects.py | 86 - .../lib.linux-i686-2.7/tests/TestCadQuery.py | 1298 --------- .../lib.linux-i686-2.7/tests/TestExporters.py | 43 - .../lib.linux-i686-2.7/tests/TestImporters.py | 54 - .../tests/TestWorkplanes.py | 125 - build/lib.linux-i686-2.7/tests/__init__.py | 54 - 20 files changed, 7900 deletions(-) delete mode 100644 build/lib.linux-i686-2.7/cadquery/__init__.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/contrib/__init__.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/cq.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/cq_directive.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/cqgi.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/freecad_impl/__init__.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/freecad_impl/exporters.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/freecad_impl/geom.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/freecad_impl/importers.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/freecad_impl/shapes.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/plugins/__init__.py delete mode 100644 build/lib.linux-i686-2.7/cadquery/selectors.py delete mode 100644 build/lib.linux-i686-2.7/tests/TestCQGI.py delete mode 100644 build/lib.linux-i686-2.7/tests/TestCQSelectors.py delete mode 100644 build/lib.linux-i686-2.7/tests/TestCadObjects.py delete mode 100644 build/lib.linux-i686-2.7/tests/TestCadQuery.py delete mode 100644 build/lib.linux-i686-2.7/tests/TestExporters.py delete mode 100644 build/lib.linux-i686-2.7/tests/TestImporters.py delete mode 100644 build/lib.linux-i686-2.7/tests/TestWorkplanes.py delete mode 100644 build/lib.linux-i686-2.7/tests/__init__.py diff --git a/build/lib.linux-i686-2.7/cadquery/__init__.py b/build/lib.linux-i686-2.7/cadquery/__init__.py deleted file mode 100644 index 05f7120..0000000 --- a/build/lib.linux-i686-2.7/cadquery/__init__.py +++ /dev/null @@ -1,21 +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 * -from .cq import * - - -__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.3.0" diff --git a/build/lib.linux-i686-2.7/cadquery/contrib/__init__.py b/build/lib.linux-i686-2.7/cadquery/contrib/__init__.py deleted file mode 100644 index 67c7b68..0000000 --- a/build/lib.linux-i686-2.7/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/build/lib.linux-i686-2.7/cadquery/cq.py b/build/lib.linux-i686-2.7/cadquery/cq.py deleted file mode 100644 index 59a1180..0000000 --- a/build/lib.linux-i686-2.7/cadquery/cq.py +++ /dev/null @@ -1,2467 +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, centerOption='CenterOfMass'): - """ - 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.") - - if centerOption == 'CenterOfMass': - center = Shape.CombinedCenter(self.objects) - elif centerOption == 'CenterOfBoundBox': - center = Shape.CombinedCenterOfBoundBox(self.objects) - - normal = self.objects[0].normalAt() - xDir = _computeXdir(normal) - - else: - obj = self.objects[0] - - if isinstance(obj, Face): - if centerOption == 'CenterOfMass': - center = obj.Center() - elif centerOption == 'CenterOfBoundBox': - center = obj.CenterOfBoundBox() - normal = obj.normalAt(center) - xDir = _computeXdir(normal) - else: - if hasattr(obj, 'Center'): - if centerOption == 'CenterOfMass': - center = obj.Center() - elif centerOption == 'CenterOfBoundBox': - center = obj.CenterOfBoundBox() - 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.normalized().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/build/lib.linux-i686-2.7/cadquery/cq_directive.py b/build/lib.linux-i686-2.7/cadquery/cq_directive.py deleted file mode 100644 index 0dc5fae..0000000 --- a/build/lib.linux-i686-2.7/cadquery/cq_directive.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -A special directive for including a cq object. - -""" - -import traceback -from cadquery import * -from cadquery import cqgi -import StringIO -from docutils.parsers.rst import directives - -template = """ - -.. raw:: html - -
- %(out_svg)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 - out_svg = "Your Script Did not assign call build_output() function!" - - try: - _s = StringIO.StringIO() - result = cqgi.parse(plot_code).build() - - if result.success: - exporters.exportShape(result.first_result, "SVG", _s) - out_svg = _s.getvalue() - else: - raise result.exception - - except Exception: - traceback.print_exc() - out_svg = traceback.format_exc() - - # now out - # Now start generating the lines of output - lines = [] - - # get rid of new lines - out_svg = out_svg.replace('\n', '') - - txt_align = "left" - if "align" in options: - txt_align = 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/build/lib.linux-i686-2.7/cadquery/cqgi.py b/build/lib.linux-i686-2.7/cadquery/cqgi.py deleted file mode 100644 index 92fd860..0000000 --- a/build/lib.linux-i686-2.7/cadquery/cqgi.py +++ /dev/null @@ -1,425 +0,0 @@ -""" -The CadQuery Gateway Interface. -Provides classes and tools for executing CadQuery scripts -""" -import ast -import traceback -import time -import cadquery - -CQSCRIPT = "" - -def parse(script_source): - """ - Parses the script as a model, and returns a model. - - If you would prefer to access the underlying model without building it, - for example, to inspect its available parameters, construct a CQModel object. - - :param script_source: the script to run. Must be a valid cadquery script - :return: a CQModel object that defines the script and allows execution - - """ - model = CQModel(script_source) - return model - - -class CQModel(object): - """ - Represents a Cadquery Script. - - After construction, the metadata property contains - a ScriptMetaData object, which describes the model in more detail, - and can be used to retrive the parameters defined by the model. - - the build method can be used to generate a 3d model - """ - - def __init__(self, script_source): - """ - Create an object by parsing the supplied python script. - :param script_source: a python script to parse - """ - self.metadata = ScriptMetadata() - self.ast_tree = ast.parse(script_source, CQSCRIPT) - self.script_source = script_source - self._find_vars() - # TODO: pick up other scirpt metadata: - # describe - # pick up validation methods - - def _find_vars(self): - """ - Parse the script, and populate variables that appear to be - overridable. - """ - #assumption here: we assume that variable declarations - #are only at the top level of the script. IE, we'll ignore any - #variable definitions at lower levels of the script - - #we dont want to use the visit interface because here we excplicitly - #want to walk only the top level of the tree. - assignment_finder = ConstantAssignmentFinder(self.metadata) - - for node in self.ast_tree.body: - if isinstance(node, ast.Assign): - assignment_finder.visit_Assign(node) - - - def validate(self, params): - """ - Determine if the supplied parameters are valid. - NOT IMPLEMENTED YET-- raises NotImplementedError - :param params: a dictionary of parameters - - """ - raise NotImplementedError("not yet implemented") - - def build(self, build_parameters=None): - """ - Executes the script, using the optional parameters to override those in the model - :param build_parameters: a dictionary of variables. The variables must be - assignable to the underlying variable type. - :raises: Nothing. If there is an exception, it will be on the exception property of the result. - This is the interface so that we can return other information on the result, such as the build time - :return: a BuildResult object, which includes the status of the result, and either - a resulting shape or an exception - """ - if not build_parameters: - build_parameters = {} - - start = time.clock() - result = BuildResult() - - try: - self.set_param_values(build_parameters) - collector = ScriptCallback() - env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \ - .add_entry("build_object", collector.build_object).build() - - c = compile(self.ast_tree, CQSCRIPT, 'exec') - exec (c, env) - if collector.has_results(): - result.set_success_result(collector.outputObjects) - else: - raise NoOutputError("Script did not call build_object-- no output available.") - except Exception, ex: - print "Error Executing Script:" - result.set_failure_result(ex) - traceback.print_exc() - print "Full Text of Script:" - print self.script_source - - end = time.clock() - result.buildTime = end - start - return result - - def set_param_values(self, params): - model_parameters = self.metadata.parameters - - for k, v in params.iteritems(): - if k not in model_parameters: - raise InvalidParameterError("Cannot set value '%s': not a parameter of the model." % k) - - p = model_parameters[k] - p.set_value(v) - - -class BuildResult(object): - """ - The result of executing a CadQuery script. - The success property contains whether the exeuction was successful. - - If successful, the results property contains a list of all results, - and the first_result property contains the first result. - - If unsuccessful, the exception property contains a reference to - the stack trace that occurred. - """ - def __init__(self): - self.buildTime = None - self.results = [] - self.first_result = None - self.success = False - self.exception = None - - def set_failure_result(self, ex): - self.exception = ex - self.success = False - - def set_success_result(self, results): - self.results = results - self.first_result = self.results[0] - self.success = True - - -class ScriptMetadata(object): - """ - Defines the metadata for a parsed CQ Script. - the parameters property is a dict of InputParameter objects. - """ - def __init__(self): - self.parameters = {} - - def add_script_parameter(self, p): - self.parameters[p.name] = p - - -class ParameterType(object): - pass - - -class NumberParameterType(ParameterType): - pass - - -class StringParameterType(ParameterType): - pass - - -class BooleanParameterType(ParameterType): - pass - - -class InputParameter: - """ - Defines a parameter that can be supplied when the model is executed. - - Name, varType, and default_value are always available, because they are computed - from a variable assignment line of code: - - The others are only available if the script has used define_parameter() to - provide additional metadata - - """ - def __init__(self): - - #: the default value for the variable. - self.default_value = None - - #: the name of the parameter. - self.name = None - - #: type of the variable: BooleanParameter, StringParameter, NumericParameter - self.varType = None - - #: help text describing the variable. Only available if the script used describe_parameter() - self.shortDesc = None - - - - #: valid values for the variable. Only available if the script used describe_parameter() - self.valid_values = [] - - - - self.ast_node = None - - @staticmethod - def create(ast_node, var_name, var_type, default_value, valid_values=None, short_desc=None): - - if valid_values is None: - valid_values = [] - - p = InputParameter() - p.ast_node = ast_node - p.default_value = default_value - p.name = var_name - if short_desc is None: - p.shortDesc = var_name - else: - p.shortDesc = short_desc - p.varType = var_type - p.valid_values = valid_values - return p - - def set_value(self, new_value): - - if len(self.valid_values) > 0 and new_value not in self.valid_values: - raise InvalidParameterError( - "Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} " - .format(str(new_value), self.name, str(self.valid_values))) - - if self.varType == NumberParameterType: - try: - f = float(new_value) - self.ast_node.n = f - except ValueError: - raise InvalidParameterError( - "Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric." - .format(str(new_value), self.name)) - - elif self.varType == StringParameterType: - self.ast_node.s = str(new_value) - elif self.varType == BooleanParameterType: - if new_value: - self.ast_node.id = 'True' - else: - self.ast_node.id = 'False' - else: - raise ValueError("Unknown Type of var: ", str(self.varType)) - - def __str__(self): - return "InputParameter: {name=%s, type=%s, defaultValue=%s" % ( - self.name, str(self.varType), str(self.default_value)) - - -class ScriptCallback(object): - """ - Allows a script to communicate with the container - the build_object() method is exposed to CQ scripts, to allow them - to return objects to the execution environment - """ - - def __init__(self): - self.outputObjects = [] - - def build_object(self, shape): - """ - return an object to the executing environment - :param shape: a cadquery object - """ - self.outputObjects.append(shape) - - def describe_parameter(self,var, valid_values, short_desc): - """ - Not yet implemented: allows a script to document - extra metadata about the parameters - """ - pass - - def add_error(self, param, field_list): - """ - Not implemented yet: allows scripts to indicate that there are problems with inputs - """ - pass - - def has_results(self): - return len(self.outputObjects) > 0 - - - -class InvalidParameterError(Exception): - """ - Raised when an attempt is made to provide a new parameter value - that cannot be assigned to the model - """ - pass - - -class NoOutputError(Exception): - """ - Raised when the script does not execute the build_object() method to - return a solid - """ - pass - - -class ScriptExecutionError(Exception): - """ - Represents a script syntax error. - Useful for helping clients pinpoint issues with the script - interactively - """ - - def __init__(self, line=None, message=None): - if line is None: - self.line = 0 - else: - self.line = line - - if message is None: - self.message = "Unknown Script Error" - else: - self.message = message - - def full_message(self): - return self.__repr__() - - def __str__(self): - return self.__repr__() - - def __repr__(self): - return "ScriptError [Line %s]: %s" % (self.line, self.message) - - -class EnvironmentBuilder(object): - """ - Builds an execution environment for a cadquery script. - The environment includes the builtins, as well as - the other methods the script will need. - """ - def __init__(self): - self.env = {} - - def with_real_builtins(self): - return self.with_builtins(__builtins__) - - def with_builtins(self, env_dict): - self.env['__builtins__'] = env_dict - return self - - def with_cadquery_objects(self): - self.env['cadquery'] = cadquery - self.env['cq'] = cadquery - return self - - def add_entry(self, name, value): - self.env[name] = value - return self - - def build(self): - return self.env - - -class ConstantAssignmentFinder(ast.NodeTransformer): - """ - Visits a parse tree, and adds script parameters to the cqModel - """ - - def __init__(self, cq_model): - self.cqModel = cq_model - - def handle_assignment(self, var_name, value_node): - - - - try: - - if type(value_node) == ast.Num: - self.cqModel.add_script_parameter( - InputParameter.create(value_node, var_name, NumberParameterType, value_node.n)) - elif type(value_node) == ast.Str: - self.cqModel.add_script_parameter( - InputParameter.create(value_node, var_name, StringParameterType, value_node.s)) - elif type(value_node == ast.Name): - if value_node.id == 'True': - self.cqModel.add_script_parameter( - InputParameter.create(value_node, var_name, BooleanParameterType, True)) - elif value_node.id == 'False': - self.cqModel.add_script_parameter( - InputParameter.create(value_node, var_name, BooleanParameterType, True)) - except: - print "Unable to handle assignment for variable '%s'" % var_name - pass - - def visit_Assign(self, node): - - try: - left_side = node.targets[0] - - #do not handle attribute assignments - if isinstance(left_side,ast.Attribute): - return - - if type(node.value) in [ast.Num, ast.Str, ast.Name]: - self.handle_assignment(left_side.id, node.value) - elif type(node.value) == ast.Tuple: - # we have a multi-value assignment - for n, v in zip(left_side.elts, node.value.elts): - self.handle_assignment(n.id, v) - except: - traceback.print_exc() - print "Unable to handle assignment for node '%s'" % ast.dump(left_side) - - return node diff --git a/build/lib.linux-i686-2.7/cadquery/freecad_impl/__init__.py b/build/lib.linux-i686-2.7/cadquery/freecad_impl/__init__.py deleted file mode 100644 index 3e041e3..0000000 --- a/build/lib.linux-i686-2.7/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/build/lib.linux-i686-2.7/cadquery/freecad_impl/exporters.py b/build/lib.linux-i686-2.7/cadquery/freecad_impl/exporters.py deleted file mode 100644 index c4b097a..0000000 --- a/build/lib.linux-i686-2.7/cadquery/freecad_impl/exporters.py +++ /dev/null @@ -1,392 +0,0 @@ -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 = """ - - - - -%(hiddenContent)s - - - - -%(visibleContent)s - - - - - X - - - Y - - - Z - - - -""" - -PATHTEMPLATE="\t\t\t\n" - diff --git a/build/lib.linux-i686-2.7/cadquery/freecad_impl/geom.py b/build/lib.linux-i686-2.7/cadquery/freecad_impl/geom.py deleted file mode 100644 index 2bd8c3a..0000000 --- a/build/lib.linux-i686-2.7/cadquery/freecad_impl/geom.py +++ /dev/null @@ -1,647 +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 normalized(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.normalized() - 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.normalized() - self.yDir = self.zDir.cross(self.xDir).normalized() - - 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/build/lib.linux-i686-2.7/cadquery/freecad_impl/importers.py b/build/lib.linux-i686-2.7/cadquery/freecad_impl/importers.py deleted file mode 100644 index 7d4f0a9..0000000 --- a/build/lib.linux-i686-2.7/cadquery/freecad_impl/importers.py +++ /dev/null @@ -1,71 +0,0 @@ - -import cadquery -from .shapes import Shape - -import FreeCAD -import Part -import sys -import os -import urllib as urlreader -import tempfile - -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: - #print fileName - 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") - -#Loads a STEP file from an URL into a CQ.Workplane object -def importStepFromURL(url): - #Now read and return the shape - try: - webFile = urlreader.urlopen(url) - tempFile = tempfile.NamedTemporaryFile(suffix='.step', delete=False) - tempFile.write(webFile.read()) - webFile.close() - tempFile.close() - - rshape = Part.read(tempFile.name) - - #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 from the URL: " + url + " Could not be loaded") diff --git a/build/lib.linux-i686-2.7/cadquery/freecad_impl/shapes.py b/build/lib.linux-i686-2.7/cadquery/freecad_impl/shapes.py deleted file mode 100644 index 76af1c1..0000000 --- a/build/lib.linux-i686-2.7/cadquery/freecad_impl/shapes.py +++ /dev/null @@ -1,982 +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 - - # Helps identify this solid through the use of an ID - self.label = "" - - @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))) - - def CenterOfBoundBox(self): - 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.BoundBox.Center) - elif len(self.Solids()) == 1: - return Vector(self.Solids()[0].wrapped.BoundBox.Center) - elif len(self.Solids()) > 1: - return self.CombinedCenterOfBoundBox(self.Solids()) - elif isinstance(self.wrapped, FreeCADPart.Solid): - return Vector(self.wrapped.BoundBox.Center) - else: - raise ValueError("Cannot find the center(BoundBox's) 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)) - - @staticmethod - def CombinedCenterOfBoundBox(objects): - """ - Calculates the center of BoundBox of multiple objects. - - :param objects: a list of objects with mass 1 - """ - total_mass = len(objects) - weighted_centers = [o.wrapped.BoundBox.Center.multiply(1.0) 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): - """ - A Single Point in Space - """ - - 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 - - # Helps identify this solid through the use of an ID - self.label = "" - - 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): - """ - A trimmed curve that represents the border of a face - """ - - 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' - } - - # Helps identify this solid through the use of an ID - self.label = "" - - 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): - """ - A series of connected, ordered Edges, that typically bounds a Face - """ - - def __init__(self, obj): - """ - A Wire - """ - self.wrapped = obj - - # Helps identify this solid through the use of an ID - self.label = "" - - @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): - """ - a bounded surface that represents part of the boundary of a solid - """ - def __init__(self, obj): - - self.wrapped = obj - - self.facetypes = { - # TODO: bezier,bspline etc - FreeCADPart.Plane: 'PLANE', - FreeCADPart.Sphere: 'SPHERE', - FreeCADPart.Cone: 'CONE' - } - - # Helps identify this solid through the use of an ID - self.label = "" - - 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): - """ - the outer boundary of a surface - """ - def __init__(self, wrapped): - """ - A Shell - """ - self.wrapped = wrapped - - # Helps identify this solid through the use of an ID - self.label = "" - - @classmethod - def makeShell(cls, listOfFaces): - return Shell(FreeCADPart.makeShell([i.obj for i in listOfFaces])) - - -class Solid(Shape): - """ - a single solid - """ - def __init__(self, obj): - """ - A Solid - """ - self.wrapped = obj - - # Helps identify this solid through the use of an ID - self.label = "" - - @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 in pnt with the dimensions (length,width,height) - By 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): - """ - Make a cone with given radii and height - By 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): - """ - Make a wedge located in pnt - By default pnt=Vector(0,0,0) and dir=Vector(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): - """ - Make a sphere with a given radius - By 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 are: - (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 - (4) 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): - """ - a collection of disconnected solids - """ - - def __init__(self, obj): - """ - An Edge - """ - self.wrapped = obj - - # Helps identify this solid through the use of an ID - self.label = "" - - 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/build/lib.linux-i686-2.7/cadquery/plugins/__init__.py b/build/lib.linux-i686-2.7/cadquery/plugins/__init__.py deleted file mode 100644 index 3697b9f..0000000 --- a/build/lib.linux-i686-2.7/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/build/lib.linux-i686-2.7/cadquery/selectors.py b/build/lib.linux-i686-2.7/cadquery/selectors.py deleted file mode 100644 index be07d7b..0000000 --- a/build/lib.linux-i686-2.7/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) diff --git a/build/lib.linux-i686-2.7/tests/TestCQGI.py b/build/lib.linux-i686-2.7/tests/TestCQGI.py deleted file mode 100644 index 35d8906..0000000 --- a/build/lib.linux-i686-2.7/tests/TestCQGI.py +++ /dev/null @@ -1,170 +0,0 @@ -""" - Tests CQGI functionality - - Currently, this includes: - Parsing a script, and detecting its available variables - Altering the values at runtime - defining a build_object function to return results -""" - -from cadquery import cqgi -from tests import BaseTest -import textwrap - -TESTSCRIPT = textwrap.dedent( - """ - height=2.0 - width=3.0 - (a,b) = (1.0,1.0) - foo="bar" - - result = "%s|%s|%s|%s" % ( str(height) , str(width) , foo , str(a) ) - build_object(result) - """ -) - - -class TestCQGI(BaseTest): - def test_parser(self): - model = cqgi.CQModel(TESTSCRIPT) - metadata = model.metadata - - self.assertEquals(set(metadata.parameters.keys()), {'height', 'width', 'a', 'b', 'foo'}) - - def test_build_with_empty_params(self): - model = cqgi.CQModel(TESTSCRIPT) - result = model.build() - - self.assertTrue(result.success) - self.assertTrue(len(result.results) == 1) - self.assertTrue(result.results[0] == "2.0|3.0|bar|1.0") - - def test_build_with_different_params(self): - model = cqgi.CQModel(TESTSCRIPT) - result = model.build({'height': 3.0}) - self.assertTrue(result.results[0] == "3.0|3.0|bar|1.0") - - def test_build_with_exception(self): - badscript = textwrap.dedent( - """ - raise ValueError("ERROR") - """ - ) - - model = cqgi.CQModel(badscript) - result = model.build({}) - self.assertFalse(result.success) - self.assertIsNotNone(result.exception) - self.assertTrue(result.exception.message == "ERROR") - - def test_that_invalid_syntax_in_script_fails_immediately(self): - badscript = textwrap.dedent( - """ - this doesnt even compile - """ - ) - - with self.assertRaises(Exception) as context: - model = cqgi.CQModel(badscript) - - self.assertTrue('invalid syntax' in context.exception) - - def test_that_two_results_are_returned(self): - script = textwrap.dedent( - """ - h = 1 - build_object(h) - h = 2 - build_object(h) - """ - ) - - model = cqgi.CQModel(script) - result = model.build({}) - self.assertEquals(2, len(result.results)) - self.assertEquals(1, result.results[0]) - self.assertEquals(2, result.results[1]) - - def test_that_assinging_number_to_string_works(self): - script = textwrap.dedent( - """ - h = "this is a string" - build_object(h) - """ - ) - result = cqgi.parse(script).build( {'h': 33.33}) - self.assertEquals(result.results[0], "33.33") - - def test_that_assigning_string_to_number_fails(self): - script = textwrap.dedent( - """ - h = 20.0 - build_object(h) - """ - ) - result = cqgi.parse(script).build( {'h': "a string"}) - self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError)) - - def test_that_assigning_unknown_var_fails(self): - script = textwrap.dedent( - """ - h = 20.0 - build_object(h) - """ - ) - - result = cqgi.parse(script).build( {'w': "var is not there"}) - self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError)) - - def test_that_not_calling_build_object_raises_error(self): - script = textwrap.dedent( - """ - h = 20.0 - """ - ) - result = cqgi.parse(script).build() - self.assertTrue(isinstance(result.exception, cqgi.NoOutputError)) - - def test_that_cq_objects_are_visible(self): - script = textwrap.dedent( - """ - r = cadquery.Workplane('XY').box(1,2,3) - build_object(r) - """ - ) - - result = cqgi.parse(script).build() - self.assertTrue(result.success) - self.assertIsNotNone(result.first_result) - - def test_setting_boolean_variable(self): - script = textwrap.dedent( - """ - h = True - build_object( "*%s*" % str(h) ) - """ - ) - - #result = cqgi.execute(script) - result = cqgi.parse(script).build({'h': False}) - - self.assertTrue(result.success) - self.assertEquals(result.first_result,'*False*') - - def test_that_only_top_level_vars_are_detected(self): - script = textwrap.dedent( - """ - h = 1.0 - w = 2.0 - - def do_stuff(): - x = 1 - y = 2 - - build_object( "result" ) - """ - ) - - model = cqgi.parse(script) - - self.assertEquals(2, len(model.metadata.parameters)) \ No newline at end of file diff --git a/build/lib.linux-i686-2.7/tests/TestCQSelectors.py b/build/lib.linux-i686-2.7/tests/TestCQSelectors.py deleted file mode 100644 index f90e14b..0000000 --- a/build/lib.linux-i686-2.7/tests/TestCQSelectors.py +++ /dev/null @@ -1,358 +0,0 @@ -__author__ = 'dcowden' - -""" - Tests for CadQuery Selectors - - These tests do not construct any solids, they test only selectors that query - an existing solid - -""" - -import math -import unittest,sys -import os.path - -#my modules -from tests import BaseTest,makeUnitCube,makeUnitSquareWire -from cadquery import * -from cadquery import selectors - -class TestCQSelectors(BaseTest): - - - def testWorkplaneCenter(self): - "Test Moving workplane center" - s = Workplane(Plane.XY()) - - #current point and world point should be equal - self.assertTupleAlmostEquals((0.0,0.0,0.0),s.plane.origin.toTuple(),3) - - #move origin and confirm center moves - s.center(-2.0,-2.0) - - #current point should be 0,0, but - - self.assertTupleAlmostEquals((-2.0,-2.0,0.0),s.plane.origin.toTuple(),3) - - - def testVertices(self): - t = makeUnitSquareWire() # square box - c = CQ(t) - - self.assertEqual(4,c.vertices().size() ) - self.assertEqual(4,c.edges().size() ) - self.assertEqual(0,c.vertices().edges().size() ) #no edges on any vertices - self.assertEqual(4,c.edges().vertices().size() ) #but selecting all edges still yields all vertices - self.assertEqual(1,c.wires().size()) #just one wire - self.assertEqual(0,c.faces().size()) - self.assertEqual(0,c.vertices().faces().size()) #odd combinations all work but yield no results - self.assertEqual(0,c.edges().faces().size()) - self.assertEqual(0,c.edges().vertices().faces().size()) - - def testEnd(self): - c = CQ(makeUnitSquareWire()) - self.assertEqual(4,c.vertices().size() ) #4 because there are 4 vertices - self.assertEqual(1,c.vertices().end().size() ) #1 because we started with 1 wire - - def testAll(self): - "all returns a list of CQ objects, so that you can iterate over them individually" - c = CQ(makeUnitCube()) - self.assertEqual(6,c.faces().size()) - self.assertEqual(6,len(c.faces().all())) - self.assertEqual(4,c.faces().all()[0].vertices().size() ) - - def testFirst(self): - c = CQ( makeUnitCube()) - self.assertEqual(type(c.vertices().first().val()),Vertex) - self.assertEqual(type(c.vertices().first().first().first().val()),Vertex) - - def testCompounds(self): - c = CQ(makeUnitSquareWire()) - self.assertEqual(0,c.compounds().size() ) - self.assertEqual(0,c.shells().size() ) - self.assertEqual(0,c.solids().size() ) - - def testSolid(self): - c = CQ(makeUnitCube()) - #make sure all the counts are right for a cube - self.assertEqual(1,c.solids().size() ) - self.assertEqual(6,c.faces().size() ) - self.assertEqual(12,c.edges().size()) - self.assertEqual(8,c.vertices().size() ) - self.assertEqual(0,c.compounds().size()) - - #now any particular face should result in 4 edges and four vertices - self.assertEqual(4,c.faces().first().edges().size() ) - self.assertEqual(1,c.faces().first().size() ) - self.assertEqual(4,c.faces().first().vertices().size() ) - - self.assertEqual(4,c.faces().last().edges().size() ) - - - - def testFaceTypesFilter(self): - "Filters by face type" - c = CQ(makeUnitCube()) - self.assertEqual(c.faces().size(), c.faces('%PLANE').size()) - self.assertEqual(c.faces().size(), c.faces('%plane').size()) - self.assertEqual(0, c.faces('%sphere').size()) - self.assertEqual(0, c.faces('%cone').size()) - self.assertEqual(0, c.faces('%SPHERE').size()) - - def testPerpendicularDirFilter(self): - c = CQ(makeUnitCube()) - - self.assertEqual(8,c.edges("#Z").size() ) #8 edges are perp. to z - self.assertEqual(4, c.faces("#Z").size()) #4 faces are perp to z too! - - def testFaceDirFilter(self): - c = CQ(makeUnitCube()) - #a cube has one face in each direction - self.assertEqual(1, c.faces("+Z").size()) - self.assertEqual(1, c.faces("-Z").size()) - self.assertEqual(1, c.faces("+X").size()) - self.assertEqual(1, c.faces("X").size()) #should be same as +X - self.assertEqual(1, c.faces("-X").size()) - self.assertEqual(1, c.faces("+Y").size()) - self.assertEqual(1, c.faces("-Y").size()) - self.assertEqual(0, c.faces("XY").size()) - - def testParallelPlaneFaceFilter(self): - c = CQ(makeUnitCube()) - - #faces parallel to Z axis - self.assertEqual(2, c.faces("|Z").size()) - #TODO: provide short names for ParallelDirSelector - self.assertEqual(2, c.faces(selectors.ParallelDirSelector(Vector((0,0,1)))).size()) #same thing as above - self.assertEqual(2, c.faces(selectors.ParallelDirSelector(Vector((0,0,-1)))).size()) #same thing as above - - #just for fun, vertices on faces parallel to z - self.assertEqual(8, c.faces("|Z").vertices().size()) - - def testParallelEdgeFilter(self): - c = CQ(makeUnitCube()) - self.assertEqual(4, c.edges("|Z").size()) - self.assertEqual(4, c.edges("|X").size()) - self.assertEqual(4, c.edges("|Y").size()) - - def testMaxDistance(self): - c = CQ(makeUnitCube()) - - #should select the topmost face - self.assertEqual(1, c.faces(">Z").size()) - self.assertEqual(4, c.faces(">Z").vertices().size()) - - #vertices should all be at z=1, if this is the top face - self.assertEqual(4, len(c.faces(">Z").vertices().vals() )) - for v in c.faces(">Z").vertices().vals(): - self.assertAlmostEqual(1.0,v.Z,3) - - # test the case of multiple objects at the same distance - el = c.edges(" 0,0,1 - e = c.edges(selectors.NearestToPointSelector(t)).vals()[0] - v = c.edges(selectors.NearestToPointSelector(t)).vertices().vals() - self.assertEqual(2,len(v)) - - #nearest solid is myself - s = c.solids(selectors.NearestToPointSelector(t)).vals() - self.assertEqual(1,len(s)) - - def testBox(self): - c = CQ(makeUnitCube()) - - # test vertice selection - test_data_vertices = [ - # box point0, box point1, selected vertice - ((0.9, 0.9, 0.9), (1.1, 1.1, 1.1), (1.0, 1.0, 1.0)), - ((-0.1, 0.9, 0.9), (0.9, 1.1, 1.1), (0.0, 1.0, 1.0)), - ((-0.1, -0.1, 0.9), (0.1, 0.1, 1.1), (0.0, 0.0, 1.0)), - ((-0.1, -0.1, -0.1), (0.1, 0.1, 0.1), (0.0, 0.0, 0.0)), - ((0.9, -0.1, -0.1), (1.1, 0.1, 0.1), (1.0, 0.0, 0.0)), - ((0.9, 0.9, -0.1), (1.1, 1.1, 0.1), (1.0, 1.0, 0.0)), - ((-0.1, 0.9, -0.1), (0.1, 1.1, 0.1), (0.0, 1.0, 0.0)), - ((0.9, -0.1, 0.9), (1.1, 0.1, 1.1), (1.0, 0.0, 1.0)) - ] - - for d in test_data_vertices: - vl = c.vertices(selectors.BoxSelector(d[0], d[1])).vals() - self.assertEqual(1, len(vl)) - v = vl[0] - self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3) - - # this time box points are swapped - vl = c.vertices(selectors.BoxSelector(d[1], d[0])).vals() - self.assertEqual(1, len(vl)) - v = vl[0] - self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3) - - # test multiple vertices selection - vl = c.vertices(selectors.BoxSelector((-0.1, -0.1, 0.9),(0.1, 1.1, 1.1))).vals() - self.assertEqual(2, len(vl)) - vl = c.vertices(selectors.BoxSelector((-0.1, -0.1, -0.1),(0.1, 1.1, 1.1))).vals() - self.assertEqual(4, len(vl)) - - # test edge selection - test_data_edges = [ - # box point0, box point1, edge center - ((0.4, -0.1, -0.1), (0.6, 0.1, 0.1), (0.5, 0.0, 0.0)), - ((-0.1, -0.1, 0.4), (0.1, 0.1, 0.6), (0.0, 0.0, 0.5)), - ((0.9, 0.9, 0.4), (1.1, 1.1, 0.6), (1.0, 1.0, 0.5)), - ((0.4, 0.9, 0.9), (0.6, 1.1, 1.1,), (0.5, 1.0, 1.0)) - ] - - for d in test_data_edges: - el = c.edges(selectors.BoxSelector(d[0], d[1])).vals() - self.assertEqual(1, len(el)) - ec = el[0].Center() - self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3) - - # test again by swapping box points - el = c.edges(selectors.BoxSelector(d[1], d[0])).vals() - self.assertEqual(1, len(el)) - ec = el[0].Center() - self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3) - - # test multiple edge selection - el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals() - self.assertEqual(2, len(el)) - el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals() - self.assertEqual(3, len(el)) - - # test face selection - test_data_faces = [ - # box point0, box point1, face center - ((0.4, -0.1, 0.4), (0.6, 0.1, 0.6), (0.5, 0.0, 0.5)), - ((0.9, 0.4, 0.4), (1.1, 0.6, 0.6), (1.0, 0.5, 0.5)), - ((0.4, 0.4, 0.9), (0.6, 0.6, 1.1), (0.5, 0.5, 1.0)), - ((0.4, 0.4, -0.1), (0.6, 0.6, 0.1), (0.5, 0.5, 0.0)) - ] - - for d in test_data_faces: - fl = c.faces(selectors.BoxSelector(d[0], d[1])).vals() - self.assertEqual(1, len(fl)) - fc = fl[0].Center() - self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3) - - # test again by swapping box points - fl = c.faces(selectors.BoxSelector(d[1], d[0])).vals() - self.assertEqual(1, len(fl)) - fc = fl[0].Center() - self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3) - - # test multiple face selection - fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals() - self.assertEqual(2, len(fl)) - fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals() - self.assertEqual(3, len(fl)) - - # test boundingbox option - el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)).vals() - self.assertEqual(1, len(el)) - fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)).vals() - self.assertEqual(0, len(fl)) - fl = c.faces(selectors.BoxSelector((-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)).vals() - self.assertEqual(1, len(fl)) - - def testAndSelector(self): - c = CQ(makeUnitCube()) - - S = selectors.StringSyntaxSelector - BS = selectors.BoxSelector - - el = c.edges(selectors.AndSelector(S('|X'), BS((-2,-2,0.1), (2,2,2)))).vals() - self.assertEqual(2, len(el)) - - # test 'and' (intersection) operator - el = c.edges(S('|X') & BS((-2,-2,0.1), (2,2,2))).vals() - self.assertEqual(2, len(el)) - - def testSumSelector(self): - c = CQ(makeUnitCube()) - - S = selectors.StringSyntaxSelector - - fl = c.faces(selectors.SumSelector(S(">Z"), S("Z") + S("X"))).vals() - self.assertEqual(3, len(fl)) - - # test the subtract operator - fl = c.faces(S("#Z") - S(">X")).vals() - self.assertEqual(3, len(fl)) - - def testInverseSelector(self): - c = CQ(makeUnitCube()) - - S = selectors.StringSyntaxSelector - - fl = c.faces(selectors.InverseSelector(S('>Z'))).vals() - self.assertEqual(5, len(fl)) - el = c.faces('>Z').edges(selectors.InverseSelector(S('>X'))).vals() - self.assertEqual(3, len(el)) - - # test invert operator - fl = c.faces(-S('>Z')).vals() - self.assertEqual(5, len(fl)) - el = c.faces('>Z').edges(-S('>X')).vals() - self.assertEqual(3, len(el)) - - def testFaceCount(self): - c = CQ(makeUnitCube()) - self.assertEqual( 6, c.faces().size() ) - self.assertEqual( 2, c.faces("|Z").size() ) - - def testVertexFilter(self): - "test selecting vertices on a face" - c = CQ(makeUnitCube()) - - #TODO: filters work ok, but they are in global coordinates which sux. it would be nice - #if they were available in coordinates local to the selected face - - v2 = c.faces("+Z").vertices(" - - - - - - -""" - -TEST_RESULT_TEMPLATE=""" -

%(name)s

- %(svg)s -
- -""" - -#clean up any summary file that is in the output directory. -#i know, this sux, but there is no other way to do this in 2.6, as we cannot do class fixutres till 2.7 -writeStringToFile(SUMMARY_TEMPLATE,SUMMARY_FILE) - - -class TestCadQuery(BaseTest): - - def tearDown(self): - """ - Update summary with data from this test. - This is a really hackey way of doing it-- we get a startup event from module load, - but there is no way in unittest to get a single shutdown event-- except for stuff in 2.7 and above - - So what we do here is to read the existing file, stick in more content, and leave it - """ - svgFile = os.path.join(OUTDIR,self._testMethodName + ".svg") - - #all tests do not produce output - if os.path.exists(svgFile): - existingSummary = readFileAsString(SUMMARY_FILE) - svgText = readFileAsString(svgFile) - svgText = svgText.replace('',"") - - #now write data into the file - #the content we are replacing it with also includes the marker, so it can be replaced again - existingSummary = existingSummary.replace("", TEST_RESULT_TEMPLATE % ( - dict(svg=svgText, name=self._testMethodName))) - - writeStringToFile(existingSummary,SUMMARY_FILE) - - def saveModel(self, shape): - """ - shape must be a CQ object - Save models in SVG and STEP format - """ - shape.exportSvg(os.path.join(OUTDIR,self._testMethodName + ".svg")) - shape.val().exportStep(os.path.join(OUTDIR,self._testMethodName + ".step")) - - def testToFreeCAD(self): - """ - Tests to make sure that a CadQuery object is converted correctly to a FreeCAD object. - """ - r = Workplane('XY').rect(5, 5).extrude(5) - - r = r.toFreecad() - - self.assertEqual(12, len(r.Edges)) - - def testToSVG(self): - """ - Tests to make sure that a CadQuery object is converted correctly to SVG - """ - r = Workplane('XY').rect(5, 5).extrude(5) - - r_str = r.toSvg() - - # Make sure that a couple of sections from the SVG output make sense - self.assertTrue(r_str.index('path d=" M 2.35965 -2.27987 L 4.0114 -3.23936 "') > 0) - self.assertTrue(r_str.index('line x1="30" y1="-30" x2="58" y2="-15" stroke-width="3"') > 0) - - def testCubePlugin(self): - """ - Tests a plugin that combines cubes together with a base - :return: - """ - #make the plugin method - def makeCubes(self,length): - #self refers to the CQ or Workplane object - - #inner method that creates a cube - def _singleCube(pnt): - #pnt is a location in local coordinates - #since we're using eachpoint with useLocalCoordinates=True - return Solid.makeBox(length,length,length,pnt) - - #use CQ utility method to iterate over the stack, call our - #method, and convert to/from local coordinates. - return self.eachpoint(_singleCube,True) - - #link the plugin in - Workplane.makeCubes = makeCubes - - #call it - result = Workplane("XY").box(6.0,8.0,0.5).faces(">Z").rect(4.0,4.0,forConstruction=True).vertices() - result = result.makeCubes(1.0) - result = result.combineSolids() - self.saveModel(result) - self.assertEquals(1,result.solids().size() ) - - - def testCylinderPlugin(self): - """ - Tests a cylinder plugin. - The plugin creates cylinders of the specified radius and height for each item on the stack - - This is a very short plugin that illustrates just about the simplest possible - plugin - """ - - def cylinders(self,radius,height): - - def _cyl(pnt): - #inner function to build a cylinder - return Solid.makeCylinder(radius,height,pnt) - - #combine all the cylinders into a single compound - r = self.eachpoint(_cyl,True).combineSolids() - return r - Workplane.cyl = cylinders - - #now test. here we want weird workplane to see if the objects are transformed right - s = Workplane(Plane(Vector((0,0,0)),Vector((1,-1,0)),Vector((1,1,0)))).rect(2.0,3.0,forConstruction=True).vertices() \ - .cyl(0.25,0.5) - self.assertEquals(1,s.solids().size() ) - self.saveModel(s) - - def testPolygonPlugin(self): - """ - Tests a plugin to make regular polygons around points on the stack - - Demonstratings using eachpoint to allow working in local coordinates - to create geometry - """ - def rPoly(self,nSides,diameter): - - 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) - - return self.eachpoint(_makePolygon,True) - - Workplane.rPoly = rPoly - - s = Workplane("XY").box(4.0,4.0,0.25).faces(">Z").workplane().rect(2.0,2.0,forConstruction=True).vertices()\ - .rPoly(5,0.5).cutThruAll() - - self.assertEquals(26,s.faces().size()) #6 base sides, 4 pentagons, 5 sides each = 26 - self.saveModel(s) - - - def testPointList(self): - """ - Tests adding points and using them - """ - c = CQ(makeUnitCube()) - - s = c.faces(">Z").workplane().pushPoints([(-0.3, 0.3), (0.3, 0.3), (0, 0)]) - self.assertEqual(3, s.size()) - #TODO: is the ability to iterate over points with circle really worth it? - #maybe we should just require using all() and a loop for this. the semantics and - #possible combinations got too hard ( ie, .circle().circle() ) was really odd - body = s.circle(0.05).cutThruAll() - self.saveModel(body) - self.assertEqual(9, body.faces().size()) - - # Test the case when using eachpoint with only a blank workplane - def callback_fn(pnt): - self.assertEqual((0.0, 0.0), (pnt.x, pnt.y)) - - r = Workplane('XY') - r.objects = [] - r.eachpoint(callback_fn) - - - def testWorkplaneFromFace(self): - s = CQ(makeUnitCube()).faces(">Z").workplane() #make a workplane on the top face - r = s.circle(0.125).cutBlind(-2.0) - self.saveModel(r) - #the result should have 7 faces - self.assertEqual(7,r.faces().size() ) - self.assertEqual(type(r.val()), Solid) - self.assertEqual(type(r.first().val()),Solid) - - def testFrontReference(self): - s = CQ(makeUnitCube()).faces("front").workplane() #make a workplane on the top face - r = s.circle(0.125).cutBlind(-2.0) - self.saveModel(r) - #the result should have 7 faces - self.assertEqual(7,r.faces().size() ) - self.assertEqual(type(r.val()), Solid) - self.assertEqual(type(r.first().val()),Solid) - - def testRotate(self): - """Test solid rotation at the CQ object level.""" - box = Workplane("XY").box(1, 1, 5) - box.rotate((0, 0, 0), (1, 0, 0), 90) - startPoint = box.faces("Z").circle(1.5)\ - .workplane(offset=3.0).rect(0.75,0.5).loft(combine=True) - self.saveModel(s) - #self.assertEqual(1,s.solids().size() ) - #self.assertEqual(8,s.faces().size() ) - - def testRevolveCylinder(self): - """ - Test creating a solid using the revolve operation. - :return: - """ - # The dimensions of the model. These can be modified rather than changing the - # shape's code directly. - rectangle_width = 10.0 - rectangle_length = 10.0 - angle_degrees = 360.0 - - #Test revolve without any options for making a cylinder - result = Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve() - self.assertEqual(3, result.faces().size()) - self.assertEqual(2, result.vertices().size()) - self.assertEqual(3, result.edges().size()) - - #Test revolve when only setting the angle to revolve through - result = Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(angle_degrees) - self.assertEqual(3, result.faces().size()) - self.assertEqual(2, result.vertices().size()) - self.assertEqual(3, result.edges().size()) - result = Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(270.0) - self.assertEqual(5, result.faces().size()) - self.assertEqual(6, result.vertices().size()) - self.assertEqual(9, result.edges().size()) - - #Test when passing revolve the angle and the axis of revolution's start point - result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5)) - self.assertEqual(3, result.faces().size()) - self.assertEqual(2, result.vertices().size()) - self.assertEqual(3, result.edges().size()) - result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(270.0,(-5,-5)) - self.assertEqual(5, result.faces().size()) - self.assertEqual(6, result.vertices().size()) - self.assertEqual(9, result.edges().size()) - - #Test when passing revolve the angle and both the start and ends of the axis of revolution - result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5, -5),(-5, 5)) - self.assertEqual(3, result.faces().size()) - self.assertEqual(2, result.vertices().size()) - self.assertEqual(3, result.edges().size()) - result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(270.0,(-5, -5),(-5, 5)) - self.assertEqual(5, result.faces().size()) - self.assertEqual(6, result.vertices().size()) - self.assertEqual(9, result.edges().size()) - - #Testing all of the above without combine - result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5),(-5,5), False) - self.assertEqual(3, result.faces().size()) - self.assertEqual(2, result.vertices().size()) - self.assertEqual(3, result.edges().size()) - result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(270.0,(-5,-5),(-5,5), False) - self.assertEqual(5, result.faces().size()) - self.assertEqual(6, result.vertices().size()) - self.assertEqual(9, result.edges().size()) - - def testRevolveDonut(self): - """ - Test creating a solid donut shape with square walls - :return: - """ - # The dimensions of the model. These can be modified rather than changing the - # shape's code directly. - rectangle_width = 10.0 - rectangle_length = 10.0 - angle_degrees = 360.0 - - result = Workplane("XY").rect(rectangle_width, rectangle_length, True)\ - .revolve(angle_degrees, (20, 0), (20, 10)) - self.assertEqual(4, result.faces().size()) - self.assertEqual(4, result.vertices().size()) - self.assertEqual(6, result.edges().size()) - - def testRevolveCone(self): - """ - Test creating a solid from a revolved triangle - :return: - """ - result = Workplane("XY").lineTo(0,10).lineTo(5,0).close().revolve() - self.assertEqual(2, result.faces().size()) - self.assertEqual(2, result.vertices().size()) - self.assertEqual(3, result.edges().size()) - - def testTwistExtrude(self): - """ - Tests extrusion while twisting through an angle. - """ - profile = Workplane('XY').rect(10, 10) - r = profile.twistExtrude(10, 45, False) - - self.assertEqual(6, r.faces().size()) - - def testTwistExtrudeCombine(self): - """ - Tests extrusion while twisting through an angle, combining with other solids. - """ - profile = Workplane('XY').rect(10, 10) - r = profile.twistExtrude(10, 45) - - self.assertEqual(6, r.faces().size()) - - def testRectArray(self): - NUMX=3 - NUMY=3 - s = Workplane("XY").box(40,40,5,centered=(True,True,True)).faces(">Z").workplane().rarray(8.0,8.0,NUMX,NUMY,True).circle(2.0).extrude(2.0) - #s = Workplane("XY").box(40,40,5,centered=(True,True,True)).faces(">Z").workplane().circle(2.0).extrude(2.0) - self.saveModel(s) - self.assertEqual(6+NUMX*NUMY*2,s.faces().size()) #6 faces for the box, 2 faces for each cylinder - - def testNestedCircle(self): - s = Workplane("XY").box(40,40,5).pushPoints([(10,0),(0,10)]).circle(4).circle(2).extrude(4) - self.saveModel(s) - self.assertEqual(14,s.faces().size() ) - - def testLegoBrick(self): - #test making a simple lego brick - #which of the below - - #inputs - lbumps = 8 - wbumps = 2 - - #lego brick constants - P = 8.0 #nominal pitch - c = 0.1 #clearance on each brick side - H = 1.2 * P #nominal height of a brick - bumpDiam = 4.8 #the standard bump diameter - t = ( P - ( 2*c) - bumpDiam ) / 2.0 # the nominal thickness of the walls, normally 1.5 - - postDiam = P - t #works out to 6.5 - total_length = lbumps*P - 2.0*c - total_width = wbumps*P - 2.0*c - - #build the brick - s = Workplane("XY").box(total_length,total_width,H) #make the base - s = s.faces("Z").workplane().rarray(P,P,lbumps,wbumps,True).circle(bumpDiam/2.0).extrude(1.8) # make the bumps on the top - - #add posts on the bottom. posts are different diameter depending on geometry - #solid studs for 1 bump, tubes for multiple, none for 1x1 - tmp = s.faces(" 1 and wbumps > 1: - tmp = tmp.rarray(P,P,lbumps - 1,wbumps - 1,center=True).circle(postDiam/2.0).circle(bumpDiam/2.0).extrude(H-t) - elif lbumps > 1: - tmp = tmp.rarray(P,P,lbumps - 1,1,center=True).circle(t).extrude(H-t) - elif wbumps > 1: - tmp = tmp.rarray(P,P,1,wbumps -1,center=True).circle(t).extrude(H-t) - - self.saveModel(s) - - def testAngledHoles(self): - s = Workplane("front").box(4.0,4.0,0.25).faces(">Z").workplane().transformed(offset=Vector(0,-1.5,1.0),rotate=Vector(60,0,0))\ - .rect(1.5,1.5,forConstruction=True).vertices().hole(0.25) - self.saveModel(s) - self.assertEqual(10,s.faces().size()) - - def testTranslateSolid(self): - c = CQ(makeUnitCube()) - self.assertAlmostEqual(0.0,c.faces("Z').workplane().circle(0.125).extrude(0.5,True) #make a boss, not updating the original - self.assertEqual(8,r.faces().size()) #just the boss faces - self.assertEqual(8,c.faces().size()) #original is modified too - - def testSolidReferencesCombineTrue(self): - s = Workplane(Plane.XY()) - r = s.rect(2.0,2.0).extrude(0.5) - self.assertEqual(6,r.faces().size() ) #the result of course has 6 faces - self.assertEqual(0,s.faces().size() ) # the original workplane does not, because it did not have a solid initially - - t = r.faces(">Z").workplane().rect(0.25,0.25).extrude(0.5,True) - self.assertEqual(11,t.faces().size()) #of course the result has 11 faces - self.assertEqual(11,r.faces().size()) #r does as well. the context solid for r was updated since combine was true - self.saveModel(r) - - def testSolidReferenceCombineFalse(self): - s = Workplane(Plane.XY()) - r = s.rect(2.0,2.0).extrude(0.5) - self.assertEqual(6,r.faces().size() ) #the result of course has 6 faces - self.assertEqual(0,s.faces().size() ) # the original workplane does not, because it did not have a solid initially - - t = r.faces(">Z").workplane().rect(0.25,0.25).extrude(0.5,False) - self.assertEqual(6,t.faces().size()) #result has 6 faces, becuase it was not combined with the original - self.assertEqual(6,r.faces().size()) #original is unmodified as well - #subseuent opertions use that context solid afterwards - - def testSimpleWorkplane(self): - """ - A simple square part with a hole in it - """ - s = Workplane(Plane.XY()) - r = s.rect(2.0,2.0).extrude(0.5)\ - .faces(">Z").workplane()\ - .circle(0.25).cutBlind(-1.0) - - self.saveModel(r) - self.assertEqual(7,r.faces().size() ) - - def testMultiFaceWorkplane(self): - """ - Test Creation of workplane from multiple co-planar face - selection. - """ - s = Workplane('XY').box(1,1,1).faces('>Z').rect(1,0.5).cutBlind(-0.2) - - w = s.faces('>Z').workplane() - o = w.objects[0] # origin of the workplane - self.assertAlmostEqual(o.x, 0., 3) - self.assertAlmostEqual(o.y, 0., 3) - self.assertAlmostEqual(o.z, 0.5, 3) - - def testTriangularPrism(self): - s = Workplane("XY").lineTo(1,0).lineTo(1,1).close().extrude(0.2) - self.saveModel(s) - - def testMultiWireWorkplane(self): - """ - A simple square part with a hole in it-- but this time done as a single extrusion - with two wires, as opposed to s cut - """ - s = Workplane(Plane.XY()) - r = s.rect(2.0,2.0).circle(0.25).extrude(0.5) - - self.saveModel(r) - self.assertEqual(7,r.faces().size() ) - - def testConstructionWire(self): - """ - Tests a wire with several holes, that are based on the vertices of a square - also tests using a workplane plane other than XY - """ - s = Workplane(Plane.YZ()) - r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices().circle(0.125).extrude(0.5) - self.saveModel(r) - self.assertEqual(10,r.faces().size() ) # 10 faces-- 6 plus 4 holes, the vertices of the second rect. - - def testTwoWorkplanes(self): - """ - Tests a model that uses more than one workplane - """ - #base block - s = Workplane(Plane.XY()) - - #TODO: this syntax is nice, but the iteration might not be worth - #the complexity. - #the simpler and slightly longer version would be: - # r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices() - # for c in r.all(): - # c.circle(0.125).extrude(0.5,True) - r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices().circle(0.125).extrude(0.5) - - #side hole, blind deep 1.9 - t = r.faces(">Y").workplane().circle(0.125).cutBlind(-1.9) - self.saveModel(t) - self.assertEqual(12,t.faces().size() ) - - def testCut(self): - """ - Tests the cut function by itself to catch the case where a Solid object is passed. - """ - s = Workplane(Plane.XY()) - currentS = s.rect(2.0,2.0).extrude(0.5) - toCut = s.rect(1.0,1.0).extrude(0.5) - - currentS.cut(toCut.val()) - - self.assertEqual(10,currentS.faces().size()) - - def testCutThroughAll(self): - """ - Tests a model that uses more than one workplane - """ - #base block - s = Workplane(Plane.XY()) - r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices().circle(0.125).extrude(0.5) - - #side hole, thru all - t = r.faces(">Y").workplane().circle(0.125).cutThruAll() - self.saveModel(t) - self.assertEqual(11,t.faces().size() ) - - def testCutToFaceOffsetNOTIMPLEMENTEDYET(self): - """ - Tests cutting up to a given face, or an offset from a face - """ - #base block - s = Workplane(Plane.XY()) - r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices().circle(0.125).extrude(0.5) - - #side hole, up to 0.1 from the last face - try: - t = r.faces(">Y").workplane().circle(0.125).cutToOffsetFromFace(r.faces().mminDist(Dir.Y),0.1) - self.assertEqual(10,t.faces().size() ) #should end up being a blind hole - t.first().val().exportStep('c:/temp/testCutToFace.STEP') - except: - pass - #Not Implemented Yet - - def testWorkplaneOnExistingSolid(self): - "Tests extruding on an existing solid" - c = CQ( makeUnitCube()).faces(">Z").workplane().circle(0.25).circle(0.125).extrude(0.25) - self.saveModel(c) - self.assertEqual(10,c.faces().size() ) - - - def testWorkplaneCenterMove(self): - #this workplane is centered at x=0.5,y=0.5, the center of the upper face - s = Workplane("XY").box(1,1,1).faces(">Z").workplane().center(-0.5,-0.5) # move the center to the corner - - t = s.circle(0.25).extrude(0.2) # make a boss - self.assertEqual(9,t.faces().size() ) - self.saveModel(t) - - - def testBasicLines(self): - "Make a triangluar boss" - global OUTDIR - s = Workplane(Plane.XY()) - - #TODO: extrude() should imply wire() if not done already - #most users dont understand what a wire is, they are just drawing - - r = s.lineTo(1.0,0).lineTo(0,1.0).close().wire().extrude(0.25) - r.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesStep1.STEP')) - - self.assertEqual(0,s.faces().size()) #no faces on the original workplane - self.assertEqual(5,r.faces().size() ) # 5 faces on newly created object - - #now add a circle through a side face - r.faces("+XY").workplane().circle(0.08).cutThruAll() - self.assertEqual(6,r.faces().size()) - r.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesXY.STEP')) - - #now add a circle through a top - r.faces("+Z").workplane().circle(0.08).cutThruAll() - self.assertEqual(9,r.faces().size()) - r.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesZ.STEP')) - - self.saveModel(r) - - def test2DDrawing(self): - """ - Draw things like 2D lines and arcs, should be expanded later to include all 2D constructs - """ - s = Workplane(Plane.XY()) - r = s.lineTo(1.0, 0.0) \ - .lineTo(1.0, 1.0) \ - .threePointArc((1.0, 1.5), (0.0, 1.0)) \ - .lineTo(0.0, 0.0) \ - .moveTo(1.0, 0.0) \ - .lineTo(2.0, 0.0) \ - .lineTo(2.0, 2.0) \ - .threePointArc((2.0, 2.5), (0.0, 2.0)) \ - .lineTo(-2.0, 2.0) \ - .lineTo(-2.0, 0.0) \ - .close() - - self.assertEqual(1, r.wires().size()) - - # Test the *LineTo functions - s = Workplane(Plane.XY()) - r = s.hLineTo(1.0).vLineTo(1.0).hLineTo(0.0).close() - - self.assertEqual(1, r.wire().size()) - self.assertEqual(4, r.edges().size()) - - # Test the *Line functions - s = Workplane(Plane.XY()) - r = s.hLine(1.0).vLine(1.0).hLine(-1.0).close() - - self.assertEqual(1, r.wire().size()) - self.assertEqual(4, r.edges().size()) - - # Test the move function - s = Workplane(Plane.XY()) - r = s.move(1.0, 1.0).hLine(1.0).vLine(1.0).hLine(-1.0).close() - - self.assertEqual(1, r.wire().size()) - self.assertEqual(4, r.edges().size()) - self.assertEqual((1.0, 1.0), - (r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0)))\ - .first().val().X, - r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0)))\ - .first().val().Y)) - - def testLargestDimension(self): - """ - Tests the largestDimension function when no solids are on the stack and when there are - """ - r = Workplane('XY').box(1, 1, 1) - dim = r.largestDimension() - - self.assertAlmostEqual(8.66025403784, dim) - - r = Workplane('XY') - dim = r.largestDimension() - - self.assertEqual(-1, dim) - - def testOccBottle(self): - """ - Make the OCC bottle example. - """ - - L = 20.0 - w = 6.0 - t = 3.0 - - s = Workplane(Plane.XY()) - #draw half the profile of the bottle - p = s.center(-L/2.0,0).vLine(w/2.0).threePointArc((L/2.0, w/2.0 + t),(L,w/2.0)).vLine(-w/2.0).mirrorX()\ - .extrude(30.0,True) - - #make the neck - p.faces(">Z").workplane().circle(3.0).extrude(2.0,True) #.edges().fillet(0.05) - - #make a shell - p.faces(">Z").shell(0.3) - self.saveModel(p) - - - def testSplineShape(self): - """ - Tests making a shape with an edge that is a spline - """ - 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) - self.saveModel(r) - - def testSimpleMirror(self): - """ - Tests a simple mirroring operation - """ - s = Workplane("XY").lineTo(2, 2).threePointArc((3, 1), (2, 0)) \ - .mirrorX().extrude(0.25) - self.assertEquals(6, s.faces().size()) - self.saveModel(s) - - def testUnorderedMirror(self): - """ - Tests whether or not a wire can be mirrored if its mirror won't connect to it - """ - r = 20 - s = 7 - t = 1.5 - - points = [ - (0, t/2), - (r/2-1.5*t, r/2-t), - (s/2, r/2-t), - (s/2, r/2), - (r/2, r/2), - (r/2, s/2), - (r/2-t, s/2), - (r/2-t, r/2-1.5*t), - (t/2, 0) - ] - - r = Workplane("XY").polyline(points).mirrorX() - - self.assertEquals(1, r.wires().size()) - self.assertEquals(18, r.edges().size()) - - # def testChainedMirror(self): - # """ - # Tests whether or not calling mirrorX().mirrorY() works correctly - # """ - # r = 20 - # s = 7 - # t = 1.5 - # - # points = [ - # (0, t/2), - # (r/2-1.5*t, r/2-t), - # (s/2, r/2-t), - # (s/2, r/2), - # (r/2, r/2), - # (r/2, s/2), - # (r/2-t, s/2), - # (r/2-t, r/2-1.5*t), - # (t/2, 0) - # ] - # - # r = Workplane("XY").polyline(points).mirrorX().mirrorY() - # - # self.assertEquals(1, r.wires().size()) - # self.assertEquals(32, r.edges().size()) - - #TODO: Re-work testIbeam test below now that chaining works - #TODO: Add toLocalCoords and toWorldCoords tests - - def testIbeam(self): - """ - Make an ibeam. demonstrates fancy mirroring - """ - s = Workplane(Plane.XY()) - L = 100.0 - H = 20.0 - W = 20.0 - - t = 1.0 - #TODO: for some reason doing 1/4 of the profile and mirroring twice ( .mirrorX().mirrorY() ) - #did not work, due to a bug in freecad-- it was losing edges when creating a composite wire. - #i just side-stepped it for now - - pts = [ - (0, H/2.0), - (W/2.0, H/2.0), - (W/2.0, (H/2.0 - t)), - (t/2.0, (H/2.0-t)), - (t/2.0, (t - H/2.0)), - (W/2.0, (t - H/2.0)), - (W/2.0, H / -2.0), - (0, H/-2.0) - ] - r = s.polyline(pts).mirrorY() #these other forms also work - res = r.extrude(L) - self.saveModel(res) - - def testCone(self): - """ - Tests that a simple cone works - """ - s = Solid.makeCone(0, 1.0, 2.0) - t = CQ(s) - self.saveModel(t) - self.assertEqual(2, t.faces().size()) - - def testFillet(self): - """ - Tests filleting edges on a solid - """ - c = CQ( makeUnitCube()).faces(">Z").workplane().circle(0.25).extrude(0.25,True).edges("|Z").fillet(0.2) - self.saveModel(c) - self.assertEqual(12,c.faces().size() ) - - def testChamfer(self): - """ - Test chamfer API with a box shape - """ - cube = CQ(makeUnitCube()).faces(">Z").chamfer(0.1) - self.saveModel(cube) - self.assertEqual(10, cube.faces().size()) - - def testChamferAsymmetrical(self): - """ - Test chamfer API with a box shape for asymmetrical lengths - """ - cube = CQ(makeUnitCube()).faces(">Z").chamfer(0.1, 0.2) - self.saveModel(cube) - self.assertEqual(10, cube.faces().size()) - - # test if edge lengths are different - edge = cube.edges(">Z").vals()[0] - self.assertAlmostEqual(0.6, edge.Length(), 3) - edge = cube.edges("|Z").vals()[0] - self.assertAlmostEqual(0.9, edge.Length(), 3) - - def testChamferCylinder(self): - """ - Test chamfer API with a cylinder shape - """ - cylinder = Workplane("XY").circle(1).extrude(1).faces(">Z").chamfer(0.1) - self.saveModel(cylinder) - self.assertEqual(4, cylinder.faces().size()) - - def testCounterBores(self): - """ - Tests making a set of counterbored holes in a face - """ - c = CQ(makeCube(3.0)) - pnts = [ - (-1.0, -1.0), (0.0, 0.0), (1.0, 1.0) - ] - c.faces(">Z").workplane().pushPoints(pnts).cboreHole(0.1, 0.25, 0.25, 0.75) - self.assertEquals(18, c.faces().size()) - self.saveModel(c) - - # Tests the case where the depth of the cboreHole is not specified - c2 = CQ(makeCube(3.0)) - pnts = [ - (-1.0, -1.0), (0.0, 0.0), (1.0, 1.0) - ] - c2.faces(">Z").workplane().pushPoints(pnts).cboreHole(0.1, 0.25, 0.25) - self.assertEquals(15, c2.faces().size()) - - def testCounterSinks(self): - """ - Tests countersinks - """ - s = Workplane(Plane.XY()) - result = s.rect(2.0, 4.0).extrude(0.5).faces(">Z").workplane()\ - .rect(1.5, 3.5, forConstruction=True).vertices().cskHole(0.125, 0.25, 82, depth=None) - self.saveModel(result) - - def testSplitKeepingHalf(self): - """ - Tests splitting a solid - """ - - #drill a hole in the side - c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll() - - self.assertEqual(7, c.faces().size()) - - #now cut it in half sideways - c.faces(">Y").workplane(-0.5).split(keepTop=True) - self.saveModel(c) - self.assertEqual(8, c.faces().size()) - - def testSplitKeepingBoth(self): - """ - Tests splitting a solid - """ - - #drill a hole in the side - c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll() - self.assertEqual(7, c.faces().size()) - - #now cut it in half sideways - result = c.faces(">Y").workplane(-0.5).split(keepTop=True, keepBottom=True) - - #stack will have both halves, original will be unchanged - self.assertEqual(2, result.solids().size()) # two solids are on the stack, eac - self.assertEqual(8, result.solids().item(0).faces().size()) - self.assertEqual(8, result.solids().item(1).faces().size()) - - def testSplitKeepingBottom(self): - """ - Tests splitting a solid improperly - """ - # Drill a hole in the side - c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll() - self.assertEqual(7, c.faces().size()) - - # Now cut it in half sideways - result = c.faces(">Y").workplane(-0.5).split(keepTop=False, keepBottom=True) - - #stack will have both halves, original will be unchanged - self.assertEqual(1, result.solids().size()) # one solid is on the stack - self.assertEqual(8, result.solids().item(0).faces().size()) - - def testBoxDefaults(self): - """ - Tests creating a single box - """ - s = Workplane("XY").box(2, 3, 4) - self.assertEquals(1, s.solids().size()) - self.saveModel(s) - - def testSimpleShell(self): - """ - Create s simple box - """ - s = Workplane("XY").box(2, 2, 2).faces("+Z").shell(0.05) - self.saveModel(s) - self.assertEquals(23, s.faces().size()) - - - def testOpenCornerShell(self): - s = Workplane("XY").box(1, 1, 1) - s1 = s.faces("+Z") - s1.add(s.faces("+Y")).add(s.faces("+X")) - self.saveModel(s1.shell(0.2)) - - # Tests the list option variation of add - s1 = s.faces("+Z") - s1.add(s.faces("+Y")).add([s.faces("+X")]) - - # Tests the raw object option variation of add - s1 = s.faces("+Z") - s1.add(s.faces("+Y")).add(s.faces("+X").val().wrapped) - - def testTopFaceFillet(self): - s = Workplane("XY").box(1, 1, 1).faces("+Z").edges().fillet(0.1) - self.assertEquals(s.faces().size(), 10) - self.saveModel(s) - - def testBoxPointList(self): - """ - Tests creating an array of boxes - """ - s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().box(0.25, 0.25, 0.25, combine=True) - #1 object, 4 solids because the object is a compound - self.assertEquals(1, s.solids().size()) - self.assertEquals(1, s.size()) - self.saveModel(s) - - s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().box(0.25, 0.25, 0.25, combine=False) - #4 objects, 4 solids, because each is a separate solid - self.assertEquals(4, s.size()) - self.assertEquals(4, s.solids().size()) - - def testBoxCombine(self): - s = Workplane("XY").box(4, 4, 0.5).faces(">Z").workplane().rect(3, 3, forConstruction=True).vertices().box(0.25, 0.25, 0.25, combine=True) - - self.saveModel(s) - self.assertEquals(1, s.solids().size()) # we should have one big solid - self.assertEquals(26, s.faces().size()) # should have 26 faces. 6 for the box, and 4x5 for the smaller cubes - - def testSphereDefaults(self): - s = Workplane("XY").sphere(10) - #self.saveModel(s) # Until FreeCAD fixes their sphere operation - self.assertEquals(1, s.solids().size()) - self.assertEquals(1, s.faces().size()) - - def testSphereCustom(self): - s = Workplane("XY").sphere(10, angle1=0, angle2=90, angle3=360, centered=(False, False, False)) - self.saveModel(s) - self.assertEquals(1, s.solids().size()) - self.assertEquals(2, s.faces().size()) - - def testSpherePointList(self): - s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().sphere(0.25, combine=False) - #self.saveModel(s) # Until FreeCAD fixes their sphere operation - self.assertEquals(4, s.solids().size()) - self.assertEquals(4, s.faces().size()) - - def testSphereCombine(self): - s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().sphere(0.25, combine=True) - #self.saveModel(s) # Until FreeCAD fixes their sphere operation - self.assertEquals(1, s.solids().size()) - self.assertEquals(4, s.faces().size()) - - def testQuickStartXY(self): - 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) - self.assertEquals(1, s.solids().size()) - self.assertEquals(14, s.faces().size()) - self.saveModel(s) - - def testQuickStartYZ(self): - s = Workplane(Plane.YZ()).box(2, 4, 0.5).faces(">X").workplane().rect(1.5, 3.5, forConstruction=True)\ - .vertices().cskHole(0.125, 0.25, 82, depth=None) - self.assertEquals(1, s.solids().size()) - self.assertEquals(14, s.faces().size()) - self.saveModel(s) - - def testQuickStartXZ(self): - s = Workplane(Plane.XZ()).box(2, 4, 0.5).faces(">Y").workplane().rect(1.5, 3.5, forConstruction=True)\ - .vertices().cskHole(0.125, 0.25, 82, depth=None) - self.assertEquals(1, s.solids().size()) - self.assertEquals(14, s.faces().size()) - self.saveModel(s) - - def testDoubleTwistedLoft(self): - s = Workplane("XY").polygon(8, 20.0).workplane(offset=4.0).transformed(rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft() - s2 = Workplane("XY").polygon(8, 20.0).workplane(offset=-4.0).transformed(rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft() - #self.assertEquals(10,s.faces().size()) - #self.assertEquals(1,s.solids().size()) - s3 = s.combineSolids(s2) - self.saveModel(s3) - - def testTwistedLoft(self): - s = Workplane("XY").polygon(8,20.0).workplane(offset=4.0).transformed(rotate=Vector(0,0,15.0)).polygon(8,20).loft() - self.assertEquals(10,s.faces().size()) - self.assertEquals(1,s.solids().size()) - self.saveModel(s) - - def testUnions(self): - #duplicates a memory problem of some kind reported when combining lots of objects - s = Workplane("XY").rect(0.5,0.5).extrude(5.0) - o = [] - beginTime = time.time() - for i in range(15): - t = Workplane("XY").center(10.0*i,0).rect(0.5,0.5).extrude(5.0) - o.append(t) - - #union stuff - for oo in o: - s = s.union(oo) - print "Total time %0.3f" % (time.time() - beginTime) - - #Test unioning a Solid object - s = Workplane(Plane.XY()) - currentS = s.rect(2.0,2.0).extrude(0.5) - toUnion = s.rect(1.0,1.0).extrude(1.0) - - currentS.union(toUnion.val(), combine=False) - - #TODO: When unioning and combining is figured out, uncomment the following assert - #self.assertEqual(10,currentS.faces().size()) - - def testCombine(self): - s = Workplane(Plane.XY()) - objects1 = s.rect(2.0,2.0).extrude(0.5).faces('>Z').rect(1.0,1.0).extrude(0.5) - - objects1.combine() - - self.assertEqual(11, objects1.faces().size()) - - - def testCombineSolidsInLoop(self): - #duplicates a memory problem of some kind reported when combining lots of objects - s = Workplane("XY").rect(0.5,0.5).extrude(5.0) - o = [] - beginTime = time.time() - for i in range(15): - t = Workplane("XY").center(10.0*i,0).rect(0.5,0.5).extrude(5.0) - o.append(t) - - #append the 'good way' - for oo in o: - s.add(oo) - s = s.combineSolids() - - print "Total time %0.3f" % (time.time() - beginTime) - - self.saveModel(s) - - def testClean(self): - """ - Tests the `clean()` method which is called automatically. - """ - - # make a cube with a splitter edge on one of the faces - # autosimplify should remove the splitter - s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ - line(-10,0).close().extrude(10) - - self.assertEqual(6, s.faces().size()) - - # test removal of splitter caused by union operation - s = Workplane("XY").box(10,10,10).union(Workplane("XY").box(20,10,10)) - - self.assertEqual(6, s.faces().size()) - - # test removal of splitter caused by extrude+combine operation - s = Workplane("XY").box(10,10,10).faces(">Y").\ - workplane().rect(5,10,5).extrude(20) - - self.assertEqual(10, s.faces().size()) - - # test removal of splitter caused by double hole operation - s = Workplane("XY").box(10,10,10).faces(">Z").workplane().\ - hole(3,5).faces(">Z").workplane().hole(3,10) - - self.assertEqual(7, s.faces().size()) - - # test removal of splitter caused by cutThruAll - s = Workplane("XY").box(10,10,10).faces(">Y").workplane().\ - rect(10,5).cutBlind(-5).faces(">Z").workplane().\ - center(0,2.5).rect(5,5).cutThruAll() - - self.assertEqual(18, s.faces().size()) - - # test removal of splitter with box - s = Workplane("XY").box(5,5,5).box(10,5,2) - - self.assertEqual(14, s.faces().size()) - - def testNoClean(self): - """ - Test the case when clean is disabled. - """ - # test disabling autoSimplify - s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ - line(-10,0).close().extrude(10, clean=False) - self.assertEqual(7, s.faces().size()) - - s = Workplane("XY").box(10,10,10).\ - union(Workplane("XY").box(20,10,10), clean=False) - self.assertEqual(14, s.faces().size()) - - s = Workplane("XY").box(10,10,10).faces(">Y").\ - workplane().rect(5,10,5).extrude(20, clean=False) - - self.assertEqual(12, s.faces().size()) - - def testExplicitClean(self): - """ - Test running of `clean()` method explicitly. - """ - s = Workplane("XY").moveTo(0,0).line(5,0).line(5,0).line(0,10).\ - line(-10,0).close().extrude(10,clean=False).clean() - self.assertEqual(6, s.faces().size()) - - def testCup(self): - - """ - UOM = "mm" - - # - # PARAMETERS and PRESETS - # These parameters can be manipulated by end users - # - bottomDiameter = FloatParam(min=10.0,presets={'default':50.0,'tumbler':50.0,'shot':35.0,'tea':50.0,'saucer':100.0},group="Basics", desc="Bottom diameter") - topDiameter = FloatParam(min=10.0,presets={'default':85.0,'tumbler':85.0,'shot':50.0,'tea':51.0,'saucer':400.0 },group="Basics", desc="Top diameter") - thickness = FloatParam(min=0.1,presets={'default':2.0,'tumbler':2.0,'shot':2.66,'tea':2.0,'saucer':2.0},group="Basics", desc="Thickness") - height = FloatParam(min=1.0,presets={'default':80.0,'tumbler':80.0,'shot':59.0,'tea':125.0,'saucer':40.0},group="Basics", desc="Overall height") - lipradius = FloatParam(min=1.0,presets={'default':1.0,'tumbler':1.0,'shot':0.8,'tea':1.0,'saucer':1.0},group="Basics", desc="Lip Radius") - bottomThickness = FloatParam(min=1.0,presets={'default':5.0,'tumbler':5.0,'shot':10.0,'tea':10.0,'saucer':5.0},group="Basics", desc="BottomThickness") - - # - # Your build method. It must return a solid object - # - def build(): - br = bottomDiameter.value / 2.0 - tr = topDiameter.value / 2.0 - t = thickness.value - s1 = Workplane("XY").circle(br).workplane(offset=height.value).circle(tr).loft() - s2 = Workplane("XY").workplane(offset=bottomThickness.value).circle(br - t ).workplane(offset=height.value - t ).circle(tr - t).loft() - - cup = s1.cut(s2) - cup.faces(">Z").edges().fillet(lipradius.value) - return cup - """ - - #for some reason shell doesnt work on this simple shape. how disappointing! - td = 50.0 - bd = 20.0 - h = 10.0 - t = 1.0 - s1 = Workplane("XY").circle(bd).workplane(offset=h).circle(td).loft() - s2 = Workplane("XY").workplane(offset=t).circle(bd-(2.0*t)).workplane(offset=(h-t)).circle(td-(2.0*t)).loft() - s3 = s1.cut(s2) - self.saveModel(s3) - - - def testEnclosure(self): - """ - Builds an electronics enclosure - Original FreeCAD script: 81 source statements ,not including variables - This script: 34 - """ - - #parameter definitions - p_outerWidth = 100.0 #Outer width of box enclosure - p_outerLength = 150.0 #Outer length of box enclosure - p_outerHeight = 50.0 #Outer height of box enclosure - - p_thickness = 3.0 #Thickness of the box walls - p_sideRadius = 10.0 #Radius for the curves around the sides of the bo - p_topAndBottomRadius = 2.0 #Radius for the curves on the top and bottom edges of the box - - p_screwpostInset = 12.0 #How far in from the edges the screwposts should be place. - p_screwpostID = 4.0 #nner Diameter of the screwpost holes, should be roughly screw diameter not including threads - p_screwpostOD = 10.0 #Outer Diameter of the screwposts.\nDetermines overall thickness of the posts - - p_boreDiameter = 8.0 #Diameter of the counterbore hole, if any - p_boreDepth = 1.0 #Depth of the counterbore hole, if - p_countersinkDiameter = 0.0 #Outer diameter of countersink. Should roughly match the outer diameter of the screw head - p_countersinkAngle = 90.0 #Countersink angle (complete angle between opposite sides, not from center to one side) - p_flipLid = True #Whether to place the lid with the top facing down or not. - p_lipHeight = 1.0 #Height of lip on the underside of the lid.\nSits inside the box body for a snug fit. - - #outer shell - oshell = Workplane("XY").rect(p_outerWidth,p_outerLength).extrude(p_outerHeight + p_lipHeight) - - #weird geometry happens if we make the fillets in the wrong order - if p_sideRadius > p_topAndBottomRadius: - oshell.edges("|Z").fillet(p_sideRadius) - oshell.edges("#Z").fillet(p_topAndBottomRadius) - else: - oshell.edges("#Z").fillet(p_topAndBottomRadius) - oshell.edges("|Z").fillet(p_sideRadius) - - #inner shell - ishell = oshell.faces("Z").workplane(-p_thickness)\ - .rect(POSTWIDTH,POSTLENGTH,forConstruction=True)\ - .vertices() - - for v in postCenters.all(): - v.circle(p_screwpostOD/2.0).circle(p_screwpostID/2.0)\ - .extrude((-1.0)*(p_outerHeight + p_lipHeight -p_thickness ),True) - - #split lid into top and bottom parts - (lid,bottom) = box.faces(">Z").workplane(-p_thickness -p_lipHeight ).split(keepTop=True,keepBottom=True).all() #splits into two solids - - #translate the lid, and subtract the bottom from it to produce the lid inset - lowerLid = lid.translate((0,0,-p_lipHeight)) - cutlip = lowerLid.cut(bottom).translate((p_outerWidth + p_thickness ,0,p_thickness - p_outerHeight + p_lipHeight)) - - #compute centers for counterbore/countersink or counterbore - topOfLidCenters = cutlip.faces(">Z").workplane().rect(POSTWIDTH,POSTLENGTH,forConstruction=True).vertices() - - #add holes of the desired type - if p_boreDiameter > 0 and p_boreDepth > 0: - topOfLid = topOfLidCenters.cboreHole(p_screwpostID,p_boreDiameter,p_boreDepth,(2.0)*p_thickness) - elif p_countersinkDiameter > 0 and p_countersinkAngle > 0: - topOfLid = topOfLidCenters.cskHole(p_screwpostID,p_countersinkDiameter,p_countersinkAngle,(2.0)*p_thickness) - else: - topOfLid= topOfLidCenters.hole(p_screwpostID,(2.0)*p_thickness) - - #flip lid upside down if desired - if p_flipLid: - topOfLid.rotateAboutCenter((1,0,0),180) - - #return the combined result - result =topOfLid.union(bottom) - - self.saveModel(result) diff --git a/build/lib.linux-i686-2.7/tests/TestExporters.py b/build/lib.linux-i686-2.7/tests/TestExporters.py deleted file mode 100644 index 2c7b9f4..0000000 --- a/build/lib.linux-i686-2.7/tests/TestExporters.py +++ /dev/null @@ -1,43 +0,0 @@ -""" - Tests basic workplane functionality -""" -#core modules -import StringIO - -#my modules -from cadquery import * -from cadquery import exporters -from tests import BaseTest - -class TestExporters(BaseTest): - - def _exportBox(self,eType,stringsToFind): - """ - Exports a test object, and then looks for - all of the supplied strings to be in the result - returns the result in case the case wants to do more checks also - """ - p = Workplane("XY").box(1,2,3) - s = StringIO.StringIO() - exporters.exportShape(p,eType,s,0.1) - - result = s.getvalue() - #print result - for q in stringsToFind: - self.assertTrue(result.find(q) > -1 ) - return result - - def testSTL(self): - self._exportBox(exporters.ExportTypes.STL,['facet normal']) - - def testSVG(self): - self._exportBox(exporters.ExportTypes.SVG,['']) - - def testSTEP(self): - self._exportBox(exporters.ExportTypes.STEP,['FILE_SCHEMA']) - - def testTJS(self): - self._exportBox(exporters.ExportTypes.TJS,['vertices','formatVersion','faces']) diff --git a/build/lib.linux-i686-2.7/tests/TestImporters.py b/build/lib.linux-i686-2.7/tests/TestImporters.py deleted file mode 100644 index edee5d5..0000000 --- a/build/lib.linux-i686-2.7/tests/TestImporters.py +++ /dev/null @@ -1,54 +0,0 @@ -""" - Tests file importers such as STEP -""" -#core modules -import StringIO - -from cadquery import * -from cadquery import exporters -from cadquery import importers -from tests import BaseTest - -#where unit test output will be saved -import sys -if sys.platform.startswith("win"): - OUTDIR = "c:/temp" -else: - OUTDIR = "/tmp" - - -class TestImporters(BaseTest): - def importBox(self, importType, fileName): - """ - Exports a simple box to a STEP file and then imports it again - :param importType: The type of file we're importing (STEP, STL, etc) - :param fileName: The path and name of the file to write to - """ - #We're importing a STEP file - if importType == importers.ImportTypes.STEP: - #We first need to build a simple shape to export - shape = Workplane("XY").box(1, 2, 3).val() - - #Export the shape to a temporary file - shape.exportStep(fileName) - - # Reimport the shape from the new STEP file - importedShape = importers.importShape(importType,fileName) - - #Check to make sure we got a solid back - self.assertTrue(importedShape.val().ShapeType() == "Solid") - - #Check the number of faces and vertices per face to make sure we have a box shape - self.assertTrue(importedShape.faces("+X").size() == 1 and importedShape.faces("+X").vertices().size() == 4) - self.assertTrue(importedShape.faces("+Y").size() == 1 and importedShape.faces("+Y").vertices().size() == 4) - self.assertTrue(importedShape.faces("+Z").size() == 1 and importedShape.faces("+Z").vertices().size() == 4) - - def testSTEP(self): - """ - Tests STEP file import - """ - self.importBox(importers.ImportTypes.STEP, OUTDIR + "/tempSTEP.step") - -if __name__ == '__main__': - import unittest - unittest.main() diff --git a/build/lib.linux-i686-2.7/tests/TestWorkplanes.py b/build/lib.linux-i686-2.7/tests/TestWorkplanes.py deleted file mode 100644 index 838b953..0000000 --- a/build/lib.linux-i686-2.7/tests/TestWorkplanes.py +++ /dev/null @@ -1,125 +0,0 @@ -""" - Tests basic workplane functionality -""" -#core modules - -#my modules -from cadquery import * -from tests import BaseTest,toTuple - -xAxis_ = Vector(1, 0, 0) -yAxis_ = Vector(0, 1, 0) -zAxis_ = Vector(0, 0, 1) -xInvAxis_ = Vector(-1, 0, 0) -yInvAxis_ = Vector(0, -1, 0) -zInvAxis_ = Vector(0, 0, -1) - -class TestWorkplanes(BaseTest): - - def testYZPlaneOrigins(self): - #xy plane-- with origin at x=0.25 - base = Vector(0.25,0,0) - p = Plane(base, Vector(0,1,0), Vector(1,0,0)) - - #origin is always (0,0,0) in local coordinates - self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 ) - - #(0,0,0) is always the original base in global coordinates - self.assertTupleAlmostEquals(base.toTuple(), p.toWorldCoords((0,0)).toTuple() ,2 ) - - def testXYPlaneOrigins(self): - base = Vector(0,0,0.25) - p = Plane(base, Vector(1,0,0), Vector(0,0,1)) - - #origin is always (0,0,0) in local coordinates - self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 ) - - #(0,0,0) is always the original base in global coordinates - self.assertTupleAlmostEquals(toTuple(base), p.toWorldCoords((0,0)).toTuple() ,2 ) - - def testXZPlaneOrigins(self): - base = Vector(0,0.25,0) - p = Plane(base, Vector(0,0,1), Vector(0,1,0)) - - #(0,0,0) is always the original base in global coordinates - self.assertTupleAlmostEquals(toTuple(base), p.toWorldCoords((0,0)).toTuple() ,2 ) - - #origin is always (0,0,0) in local coordinates - self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 ) - - def testPlaneBasics(self): - p = Plane.XY() - #local to world - self.assertTupleAlmostEquals((1.0,1.0,0),p.toWorldCoords((1,1)).toTuple(),2 ) - self.assertTupleAlmostEquals((-1.0,-1.0,0), p.toWorldCoords((-1,-1)).toTuple(),2 ) - - #world to local - self.assertTupleAlmostEquals((-1.0,-1.0), p.toLocalCoords(Vector(-1,-1,0)).toTuple() ,2 ) - self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(1,1,0)).toTuple() ,2 ) - - p = Plane.YZ() - self.assertTupleAlmostEquals((0,1.0,1.0),p.toWorldCoords((1,1)).toTuple() ,2 ) - - #world to local - self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(0,1,1)).toTuple() ,2 ) - - p = Plane.XZ() - r = p.toWorldCoords((1,1)).toTuple() - self.assertTupleAlmostEquals((1.0,0.0,1.0),r ,2 ) - - #world to local - self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(1,0,1)).toTuple() ,2 ) - - def testOffsetPlanes(self): - "Tests that a plane offset from the origin works ok too" - p = Plane.XY(origin=(10.0,10.0,0)) - - - self.assertTupleAlmostEquals((11.0,11.0,0.0),p.toWorldCoords((1.0,1.0)).toTuple(),2 ) - self.assertTupleAlmostEquals((2.0,2.0), p.toLocalCoords(Vector(12.0,12.0,0)).toTuple() ,2 ) - - #TODO test these offsets in the other dimensions too - p = Plane.YZ(origin=(0,2,2)) - self.assertTupleAlmostEquals((0.0,5.0,5.0), p.toWorldCoords((3.0,3.0)).toTuple() ,2 ) - self.assertTupleAlmostEquals((10,10.0,0.0), p.toLocalCoords(Vector(0.0,12.0,12.0)).toTuple() ,2 ) - - p = Plane.XZ(origin=(2,0,2)) - r = p.toWorldCoords((1.0,1.0)).toTuple() - self.assertTupleAlmostEquals((3.0,0.0,3.0),r ,2 ) - self.assertTupleAlmostEquals((10.0,10.0), p.toLocalCoords(Vector(12.0,0.0,12.0)).toTuple() ,2 ) - - def testXYPlaneBasics(self): - p = Plane.named('XY') - self.assertTupleAlmostEquals(p.zDir.toTuple(), zAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4) - - def testYZPlaneBasics(self): - p = Plane.named('YZ') - self.assertTupleAlmostEquals(p.zDir.toTuple(), xAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4) - - def testZXPlaneBasics(self): - p = Plane.named('ZX') - self.assertTupleAlmostEquals(p.zDir.toTuple(), yAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4) - - def testXZPlaneBasics(self): - p = Plane.named('XZ') - self.assertTupleAlmostEquals(p.zDir.toTuple(), yInvAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4) - - def testYXPlaneBasics(self): - p = Plane.named('YX') - self.assertTupleAlmostEquals(p.zDir.toTuple(), zInvAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4) - - def testZYPlaneBasics(self): - p = Plane.named('ZY') - self.assertTupleAlmostEquals(p.zDir.toTuple(), xInvAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4) - self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4) diff --git a/build/lib.linux-i686-2.7/tests/__init__.py b/build/lib.linux-i686-2.7/tests/__init__.py deleted file mode 100644 index 5dfc40e..0000000 --- a/build/lib.linux-i686-2.7/tests/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -from cadquery import * -import unittest -import sys -import os - -import FreeCAD - -import Part as P -from FreeCAD import Vector as V - - -def readFileAsString(fileName): - f= open(fileName, 'r') - s = f.read() - f.close() - return s - - -def writeStringToFile(strToWrite, fileName): - f = open(fileName, 'w') - f.write(strToWrite) - f.close() - - -def makeUnitSquareWire(): - return Solid.cast(P.makePolygon([V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)])) - - -def makeUnitCube(): - return makeCube(1.0) - - -def makeCube(size): - return Solid.makeBox(size, size, size) - - -def toTuple(v): - """convert a vector or a vertex to a 3-tuple: x,y,z""" - pnt = v - if type(v) == FreeCAD.Base.Vector: - return (v.Point.x, v.Point.y, v.Point.z) - elif type(v) == Vector: - return v.toTuple() - else: - raise RuntimeError("dont know how to convert type %s to tuple" % str(type(v)) ) - - -class BaseTest(unittest.TestCase): - - def assertTupleAlmostEquals(self, expected, actual, places): - for i, j in zip(actual, expected): - self.assertAlmostEquals(i, j, places) - -__all__ = ['TestCadObjects', 'TestCadQuery', 'TestCQSelectors', 'TestWorkplanes', 'TestExporters', 'TestCQSelectors', 'TestImporters','TestCQGI']