diff --git a/.coverage b/.coverage index 2714bef..612f7c0 100644 Binary files a/.coverage and b/.coverage differ diff --git a/cadquery/CQ.py b/cadquery/CQ.py index 7dab95f..e0d8630 100644 --- a/cadquery/CQ.py +++ b/cadquery/CQ.py @@ -153,17 +153,17 @@ class CQ(object): def combineSolids(self, otherCQToCombine=None): """ - !!!DEPRECATED!!! use union() - Combines all solids on the current stack, and any context object, together - into a single object. + !!!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. + 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. + :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. + 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 @@ -217,13 +217,11 @@ class CQ(object): Contrast with :py:meth:`all`, which returns CQ objects for all of the items on the stack """ - res = [] return self.objects def add(self, obj): """ - adds an object or a list of objects to the stack - + 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 @@ -303,6 +301,12 @@ class CQ(object): obj = self.objects[0] 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 @@ -399,17 +403,18 @@ class CQ(object): :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 + :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. + 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. + 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!") @@ -431,11 +436,12 @@ class CQ(object): :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. + **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. """ - toReturn = self._collectProperty(objType) # all of the faces from all objects on the stack, in a single list + # A single list of all faces from all objects on the stack + toReturn = self._collectProperty(objType) if selector is not None: if type(selector) == str: @@ -448,15 +454,17 @@ class CQ(object): 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. + 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 + :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 + 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:: @@ -477,15 +485,17 @@ class CQ(object): 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. + 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. + :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 + 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:: @@ -507,13 +517,14 @@ class CQ(object): 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. + 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. + :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 @@ -537,13 +548,14 @@ class CQ(object): 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. + 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. + :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 @@ -559,13 +571,14 @@ class CQ(object): 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. + 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. + :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 @@ -575,7 +588,8 @@ class CQ(object): 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. + It is possible for single CQ object ( or even a single CAD primitive ) to contain + multiple solids. See more about selectors HERE """ @@ -583,18 +597,19 @@ class CQ(object): 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. + 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. + :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. + 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 """ @@ -602,16 +617,17 @@ class CQ(object): 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. + 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. + :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. + 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 """ @@ -619,52 +635,50 @@ class CQ(object): def toSvg(self, opts=None): """ - Returns svg text that represents the first item on the stack. + Returns svg text that represents the first item on the stack. - for testing purposes. + for testing purposes. - :param opts: svg formatting options - :type opts: dictionary, width and height - :return: a string that contains SVG that represents this item. + :param opts: svg formatting options + :type opts: dictionary, width and height + :return: a string that contains SVG that represents this item. """ return SVGexporter.getSVG(self.val().wrapped, opts) def exportSvg(self, fileName): """ - Exports the first item on the stack as an SVG file + 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 + 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 + 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. + 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. + :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. + 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 @@ -678,7 +692,9 @@ class CQ(object): 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. + """ + 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 @@ -694,58 +710,61 @@ class CQ(object): A version of this method that returns a transformed copy instead of modifying the originals. """ - return self.newObject([o.rotate(axisStartPoint, axisEndPoint, angleDegrees) for o in self.objects]) + 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. + 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 + :param tupleDistance: distance to move, in global coordinates + :type tupleDistance: a 3-tuple of float + :returns: a CQ object - WARNING: the underlying objects are modified, not copied. + WARNING: the underlying objects are modified, not copied. - Future Enhancements: - A version of this method that returns a transformed copy instead - of modifying the originals. + Future Enhancements: + A version of this method that returns a transformed copy instead + of modifying the originals. """ 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. + 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. + 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. + :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:: + 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) + 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:: + 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)) + 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 + 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 + **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 + Future Enhancements: + Better selectors to make it easier to select multiple faces """ solidRef = self.findSolid() @@ -759,23 +778,24 @@ class CQ(object): def fillet(self, radius): """ - Fillets a solid on the selected edges. + 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. + 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. + :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:: + This example will create a unit cube, with the top edges filleted:: - s = Workplane().box(1,1,1).faces("+Z").edges().fillet(0.1) + 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, fe segfault + # 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() @@ -790,50 +810,52 @@ class CQ(object): class Workplane(CQ): """ - Defines a coordinate system in space, in which 2-d coordinates can be used. + 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. + :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:: + The most common use is:: - s = Workplane("XY") + 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. + 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` + .. 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 + 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. + :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:: + The most common use is:: - s = Workplane("XY") + 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. + 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': @@ -849,16 +871,18 @@ class Workplane(CQ): self.obj = obj self.plane = tmpPlane self.firstPoint = None - self.objects = [self.plane.origin] # changed so that workplane has the center as the first item on the stack + # 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 + 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 @@ -880,15 +904,14 @@ class Workplane(CQ): def newObject(self, objlist): """ - Create a new workplane object from this one. + 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. + 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 @@ -901,24 +924,24 @@ class Workplane(CQ): 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 + 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. + :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. + :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 + 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 first object on the stack is used. + WARNING: only the first object on the stack is used. - NOTE: + NOTE: """ obj = self.objects[0] p = None @@ -936,16 +959,16 @@ class Workplane(CQ): 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 + 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 + :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: @@ -969,22 +992,23 @@ class Workplane(CQ): 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 + 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. + :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:: + 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. + 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: @@ -995,30 +1019,29 @@ class Workplane(CQ): def center(self, x, y): """ - Shift local coordinates to the specified location. + Shift local coordinates to the specified location. - The location is specified in terms of local coordinates. + 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. + :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. + 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:: + 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() + #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 + 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) @@ -1027,14 +1050,13 @@ class Workplane(CQ): def lineTo(self, x, y, forConstruction=False): """ - Make a line from the current point to the provided point + 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. + :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) @@ -1050,77 +1072,77 @@ class Workplane(CQ): #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 + 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 + :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. + 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 + 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 + :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 vLineTo(self, yCoord, forConstruction=False): """ - Make a veritcal line from the current point to the provided y coordinate. + 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` + 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 + :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. + 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` + 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 + :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) def hLine(self, distance, forConstruction=False): """ - Make a horizontal line from the current point the provided distance + 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 + :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) #absolute move in current plane, not drawing def moveTo(self, x=0, y=0): """ - Move to the specified point, without drawing. + 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. + :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 ). + 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 + 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)]) @@ -1128,18 +1150,18 @@ class Workplane(CQ): #relative move in current plane, not drawing def move(self, xDist=0, yDist=0): """ - Move the specified distance from the current point, without drawing. + 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. + :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 ). + 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 + See :py:meth:`moveTo` to do the same thing but using absolute coordinates """ p = self._findFromPoint(True) newCenter = p + Vector(xDist, yDist, 0) @@ -1147,36 +1169,35 @@ class Workplane(CQ): def spline(self, listOfXYTuple, forConstruction=False): """ - Create a spline interpolated through the provided points. + 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 + :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 + 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:: + 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) + 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 + *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]) @@ -1193,19 +1214,18 @@ class Workplane(CQ): def threePointArc(self, point1, point2, forConstruction=False): """ - Draw an arc from the current point, through point1, and ending at point2 + 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 + :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) @@ -1221,21 +1241,21 @@ class Workplane(CQ): 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. + 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. + :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. + 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 + 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 + Future Enhancements: + faster implementation: this one transforms 3 times to accomplish the result """ #convert edges to a wire, if there are pending edges @@ -1257,21 +1277,21 @@ class Workplane(CQ): def mirrorY(self): """ - Mirror entities around the y axis of the workplane plane. + 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. + :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 + 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:: + 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) + s = Workplane().lineTo(2,2).threePointArc((3,1),(2,0)).mirrorX().extrude(0.25) - Produces a flat, heart shaped object + Produces a flat, heart shaped object - Future Enhancements: - mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness + Future Enhancements: + mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness """ tm = Matrix() tm.rotateY(math.pi) @@ -1279,17 +1299,17 @@ class Workplane(CQ): def mirrorX(self): """ - Mirror entities around the x axis of the workplane plane. + 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. + :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 + 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. + Typically used to make creating wires with symmetry easier. - Future Enhancements: - mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness + Future Enhancements: + mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness """ tm = Matrix() tm.rotateX(math.pi) @@ -1297,7 +1317,7 @@ class Workplane(CQ): def _addPendingEdge(self, edge): """ - Queues an edge for later combination into a wire. + Queues an edge for later combination into a wire. :param edge: :return: @@ -1309,30 +1329,30 @@ class Workplane(CQ): def _addPendingWire(self, wire): """ - Queue a Wire for later extrusion + Queue a Wire for later extrusion - Internal Processing Note. In FreeCAD, edges-->wires-->faces-->solids. + 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. + 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. + 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. + 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 + 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. + 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: @@ -1354,21 +1374,23 @@ class Workplane(CQ): def wire(self, forConstruction=False): """ - Returns a CQ object with all pending edges connected into a wire. + 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 + 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 + :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 + 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 + SEE '2-d construction concepts' for a more detailed explanation of how CadQuery handles + edges, wires, etc - Any non edges will still remain. + Any non edges will still remain. """ edges = self.ctx.pendingEdges @@ -1393,35 +1415,35 @@ class Workplane(CQ): 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. + 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 + 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 + :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 + 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. + 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 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. + 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. + 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 - + TODO: wrapper object for Wire will clean up forConstruction flag everywhere """ results = [] for obj in self.objects: @@ -1443,21 +1465,20 @@ class Workplane(CQ): 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. + 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. + :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 + :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 + 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 = [] @@ -1474,27 +1495,29 @@ class Workplane(CQ): def rect(self, xLen, yLen, centered=True, forConstruction=False): """ - Make a rectangle for each item on the stack. + 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 + :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:: + 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) + 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 + 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 @@ -1519,29 +1542,31 @@ class Workplane(CQ): #circle from current point def circle(self, radius, forConstruction=False): """ - Make a circle for each item on the stack. + 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 + :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:: + 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) + 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:: + 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) + s = Workplane().circle(2.0).circle(1.0) - Creates two concentric circles, which when extruded will form a ring. + 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 + Future Enhancements: + better way to handle forConstruction + project points not in the workplane plane onto the workplane plane """ def makeCircleWire(obj): @@ -1553,15 +1578,14 @@ class Workplane(CQ): def polygon(self, nSides, diameter): """ - Creates a polygon inscribed in a circle of the specified diameter for each point on the stack + 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 @@ -1576,19 +1600,20 @@ class Workplane(CQ): def polyline(self, listOfXYTuple, forConstruction=False): """ - Create a polyline from a list of points + 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: should the new wire be reference geometry only? - :type forConstruction: true if the wire is for reference, false if they are creating part geometry - :return: a new CQ object with the new wire on the stack + :param listOfXYTuple: a list of points in Workplane coordinates + :type listOfXYTuple: list of 2-tuples + :param forConstruction: should the new wire be reference geometry only? + :type forConstruction: true if the wire is for reference, false if they are creating + part geometry + :return: a new CQ object with the new wire on the stack - *NOTE* most commonly, the resulting wire should be closed. + *NOTE* most commonly, the resulting wire should be closed. - Future Enhancement: - This should probably yield a list of edges, not a wire, so that - it is possible to combine a polyline with other edges and arcs + Future Enhancement: + This should probably yield a list of edges, not a wire, so that + it is possible to combine a polyline with other edges and arcs """ vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple] w = Wire.makePolygon(vecs) @@ -1599,18 +1624,17 @@ class Workplane(CQ): def close(self): """ - End 2-d construction, and attempt to build a closed wire. + End 2-d construction, and attempt to build a closed wire. - :return: a CQ object with a completed wire on the stack, if possible. + :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. + 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) + 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) return self.wire() @@ -1620,10 +1644,10 @@ class Workplane(CQ): 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: + :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 - #the time this works. but a stronger implementation would be to search all solids. + #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 @@ -1634,7 +1658,8 @@ class Workplane(CQ): """ Evaluates the provided function at each point on the stack ( ie, eachpoint ) and then cuts the result from the context solid. - :param function: a function suitable for use in the eachpoint method: ie, that accepts a vector + :param fcn: a function suitable for use in the eachpoint method: ie, that accepts + a vector :param useLocalCoords: same as for :py:meth:`eachpoint` :return: a CQ object that contains the resulting solid :raises: an error if there is not a context solid to cut from @@ -1655,40 +1680,41 @@ class Workplane(CQ): #but parameter list is different so a simple function pointer wont work def cboreHole(self, diameter, cboreDiameter, cboreDepth, depth=None): """ - Makes a counterbored hole for each item on the stack. + 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 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. - The surface of the hole is at the current workplane plane. + 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:: + 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) + 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. + 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. + **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 + 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 + 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 @@ -1705,31 +1731,32 @@ class Workplane(CQ): #but parameter list is different so a simple function pointer wont work def cskHole(self, diameter, cskDiameter, cskAngle, depth=None): """ - Makes a countersunk hole for each item on the stack. + 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 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. - The surface of the hole is at the current workplane. + 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:: + 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) + 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. + 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. + **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 + see :py:meth:`cboreHole` to make counterbores instead of countersinks """ if depth is None: @@ -1754,40 +1781,41 @@ class Workplane(CQ): #but parameter list is different so a simple function pointer wont work def hole(self, diameter, depth=None): """ - Makes a hole for each item on the stack. + 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 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. - The surface of the hole is at the current workplane. + 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:: + 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) + 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. + 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. + **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 + 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 + 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! + hole = Solid.makeCylinder(diameter / 2.0, depth, center, boreDir) # local coordinates! return hole return self.cutEach(_makeHole, True) @@ -1795,40 +1823,42 @@ class Workplane(CQ): #TODO: duplicated code with _extrude and extrude def twistExtrude(self, distance, angleDegrees, combine=True): """ - Extrudes a wire in the direction normal to the plane, but also twists by the specified angle over the - length of the extrusion + 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 + 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. + 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. - :return: a CQ object with the resulting solid selected. + **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. + :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 + 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 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 + #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) + thisObj = Solid.extrudeLinearWithRotation(ws[0], ws[1:], self.plane.origin, + eDir, angleDegrees) if r is None: r = thisObj else: @@ -1841,25 +1871,26 @@ class Workplane(CQ): def extrude(self, distance, combine=True): """ - Use all un-extruded wires in the parent chain to create a prismatic solid. + 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. - :return: a CQ object with the resulting solid selected. + :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. + :return: a CQ object with the resulting solid selected. - extrude always *adds* material to a part. + 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: + 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. + * 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 + 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: @@ -1869,24 +1900,24 @@ class Workplane(CQ): def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True): """ - Use all un-revolved wires in the parent chain to create a solid. + 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 - :return: a CQ object with the resulting solid selected. + :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 + :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: + 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. + * 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 @@ -1894,15 +1925,18 @@ class Workplane(CQ): #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 + # 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 + # 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 + # 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: @@ -1910,7 +1944,8 @@ class Workplane(CQ): else: axisEnd = self.plane.toWorldCoords(axisEnd).toTuple() - r = self._revolve(angleDegrees, axisStart, axisEnd) # returns a Solid ( or a compound if there were multiple ) + # returns a Solid (or a compound if there were multiple) + r = self._revolve(angleDegrees, axisStart, axisEnd) if combine: return self._combineWithBase(r) else: @@ -1918,11 +1953,10 @@ class Workplane(CQ): 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 - + 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 @@ -1934,11 +1968,11 @@ class Workplane(CQ): def combine(self): """ - Attempts to combine all of the items on the items on the stack into a single item. - WARNING: all of the items must be of the same type! + Attempts to combine all of the items on the items on the stack into a single item. + WARNING: all of the items must be of the same type! - :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 + :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) @@ -1949,11 +1983,10 @@ class Workplane(CQ): def union(self, toUnion=None, combine=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 - + 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, @@ -1975,7 +2008,8 @@ class Workplane(CQ): raise ValueError("Cannot union Type '%s' " % str(type(toUnion))) #now combine with existing solid, if there is one - solidRef = self.findSolid(searchStack=True, searchParents=True) # look for parents to cut from + # look for parents to cut from + solidRef = self.findSolid(searchStack=True, searchParents=True) if combine and solidRef is not None: t = solidRef.fuse(newS) solidRef.wrapped = newS.wrapped @@ -1985,19 +2019,19 @@ class Workplane(CQ): def cut(self, toCut, combine=True): """ - Cuts the provided solid from the current solid, IE, perform a solid subtraction + 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, - :raises: ValueError if there is no solid to subtract from in the chain - :return: a CQ object with the resulting object selected + 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, + :raises: ValueError if there is no solid to subtract from in the chain + :return: a CQ object with the resulting object selected """ - solidRef = self.findSolid(searchStack=True, searchParents=True) # look for parents to cut from + # 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!!!") @@ -2016,20 +2050,21 @@ class Workplane(CQ): def cutBlind(self, distanceToCut): """ - Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. + 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. + 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 - :raises: ValueError if there is no solid to subtract from in the chain - :return: a CQ object with the resulting object selected + :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 + :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 + see :py:meth:`cutThruAll` to cut material from the entire part - Future Enhancements: - Cut Up to Surface + Future Enhancements: + Cut Up to Surface """ #first, make the object toCut = self._extrude(distanceToCut) @@ -2044,17 +2079,17 @@ class Workplane(CQ): def cutThruAll(self, positive=False): """ - Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. + 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. + 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 - :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 + :param boolean positive: True to cut in the positive direction, false to cut in the + negative direction + :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: @@ -2064,8 +2099,8 @@ class Workplane(CQ): def loft(self, filled=True, ruled=False, combine=True): """ - Make a lofted solid, through the set of wires. - :return: + Make a lofted solid, through the set of wires. + :return: a CQ object containing the created loft """ wiresToLoft = self.ctx.pendingWires self.ctx.pendingWires = [] @@ -2082,16 +2117,16 @@ class Workplane(CQ): def _extrude(self, distance): """ - Make a prismatic solid from the existing set of pending wires. + Make a prismatic solid from the existing set of pending wires. - :param distance: distance to extrude - :return: a FreeCAD solid, suitable for boolean operations. + :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. + 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 ) + Future Enhancements: + extrude along a profile (sweep) """ #group wires together into faces based on which ones are inside the others @@ -2106,27 +2141,26 @@ class Workplane(CQ): #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 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 + # 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 - """ + # 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: @@ -2137,17 +2171,17 @@ class Workplane(CQ): def _revolve(self, angleDegrees, axisStart, axisEnd): """ - Make a solid from the existing set of pending wires. + 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. + :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. + 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, []) @@ -2173,20 +2207,22 @@ class Workplane(CQ): :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)? + :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. - 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 + 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. + 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 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 @@ -2225,7 +2261,8 @@ class Workplane(CQ): #combine everything return self.union(boxes) - def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360, centered=(True, True, True), combine=True): + def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360, + centered=(True, True, True), combine=True): """ Returns a 3D sphere with the specified radius for each point on the stack @@ -2239,21 +2276,24 @@ class Workplane(CQ): :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) + :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 + 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. + 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 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 """ diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index fb2645d..65c2195 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -70,8 +70,7 @@ class TestCadQuery(BaseTest): writeStringToFile(existingSummary,SUMMARY_FILE) - - def saveModel(self,shape): + def saveModel(self, shape): """ shape must be a CQ object Save models in SVG and STEP format @@ -79,6 +78,16 @@ class TestCadQuery(BaseTest): 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 testCubePlugin(self): """ Tests a plugin that combines cubes together with a base @@ -763,6 +772,21 @@ class TestCadQuery(BaseTest): 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 @@ -786,6 +810,14 @@ class TestCadQuery(BaseTest): 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)