diff --git a/CadQuery/Libs/cadquery/CQ.py b/CadQuery/Libs/cadquery/CQ.py index f0d62e1..4854944 100644 --- a/CadQuery/Libs/cadquery/CQ.py +++ b/CadQuery/Libs/cadquery/CQ.py @@ -1261,7 +1261,7 @@ class Workplane(CQ): #attempt to consolidate wires together. consolidated = n.consolidateWires() - rotatedWires = self.plane.rotateShapes(consolidated.wires().vals(),matrix) + rotatedWires = self.plane.rotateShapes(consolidated.wires().vals(), matrix) for w in rotatedWires: consolidated.objects.append(w) @@ -1269,6 +1269,7 @@ class Workplane(CQ): #attempt again to consolidate all of the wires c = consolidated.consolidateWires() + return c def mirrorY(self): @@ -1288,7 +1289,6 @@ class Workplane(CQ): Future Enhancements: mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness - """ tm = Matrix() tm.rotateY(math.pi) @@ -1307,7 +1307,6 @@ class Workplane(CQ): Future Enhancements: mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness - """ tm = Matrix() tm.rotateX(math.pi) @@ -1349,11 +1348,9 @@ class Workplane(CQ): 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 somtimes - Additionally, it has a bug where a profile compose of two wires ( rathre 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 compose 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: @@ -2272,11 +2269,48 @@ class Workplane(CQ): return self.union(boxes) 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 + + :param radius: The radius of the sphere + :type radius: float > 0 + :param direct: The direction axis for the creation of the sphere + :type direct: A three-tuple + :param angle1: The first angle to sweep the sphere arc through + :type angle1: float > 0 + :param angle2: The second angle to sweep the sphere arc through + :type angle2: float > 0 + :param angle3: The third angle to sweep the sphere arc through + :type angle3: float > 0 + :param centered: A three-tuple of booleans that determines whether the sphere is centered on each axis origin + :param combine: Whether the results should be combined with other solids on the stack (and each other) + :type combine: true to combine shapes, false otherwise + :return: A sphere object for each point on the stack + + Centered is a tuple that describes whether the sphere should be centered on the x,y, and z axes. If true, + the sphere is centered on the respective axis relative to the workplane origin, if false, the workplane center + will represent the lower bound of the resulting sphere + + One sphere is created for each item on the current stack. If no items are on the stack, one box using + the current workplane center is created. + + If combine is true, the result will be a single object on the stack: + If a solid was found in the chain, the result is that solid with all spheres produced fused onto it + otherwise, the result is the combination of all the produced boxes + + If combine is false, the result will be a list of the spheres produced + """ + # Convert the direction tuple to a vector, if needed if isinstance(direct, tuple): direct = Vector(direct) def _makesphere(pnt): + """ + Inner function that is used to create a sphere for each point/object on the workplane + :param pnt: The center point for the sphere + :return: A CQ Solid object representing a sphere + """ (xp, yp, zp) = pnt.toTuple() if centered[0]: @@ -2288,6 +2322,7 @@ class Workplane(CQ): return Solid.makeSphere(radius, Vector(xp, yp, zp), direct, angle1, angle2, angle3) + # We want a sphere for each point on the workplane spheres = self.eachpoint(_makesphere, True) # If we don't need to combine everything, just return the created spheres diff --git a/CadQuery/Libs/cadquery/freecad_impl/geom.py b/CadQuery/Libs/cadquery/freecad_impl/geom.py index 7ad2cf1..1b70434 100644 --- a/CadQuery/Libs/cadquery/freecad_impl/geom.py +++ b/CadQuery/Libs/cadquery/freecad_impl/geom.py @@ -17,9 +17,10 @@ License along with this library; If not, see """ -import math,sys +import math +import cadquery import FreeCAD -#Turns out we don't need the Part module here. +import Part as FreeCADPart def sortWiresByBuildOrder(wireList,plane,result=[]): """ @@ -403,9 +404,9 @@ class Plane: correctly. """ - if isinstance(obj,Vector): + if isinstance(obj, Vector): return Vector(self.fG.multiply(obj.wrapped)) - elif isinstance(obj,Shape): + elif isinstance(obj, cadquery.Shape): return obj.transformShape(self.rG) else: raise ValueError("Dont know how to convert type %s to local coordinates" % str(type(obj))) @@ -464,7 +465,7 @@ class Plane: newP= Plane(self.origin,newXdir,newZdir) return newP - def rotateShapes(self,listOfShapes,rotationMatrix): + def rotateShapes(self, listOfShapes, rotationMatrix): """ rotate the listOfShapes by the rotationMatrix supplied. @param listOfShapes is a list of shape objects @@ -480,24 +481,42 @@ class Plane: #There might be a better way, but to do this rotation takes 3 steps #transform geometry to local coordinates #then rotate about x - #then transform back to global coordiante + #then transform back to global coordinates resultWires = [] for w in listOfShapes: mirrored = w.transformGeometry(rotationMatrix.wrapped) - resultWires.append(mirrored) + + # If the first vertex of the second wire is not coincident with the first or last vertices of the first wire + # we have to fix the wire so that it will mirror correctly + if (mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[0].X and + mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[0].Y and + mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[0].Z) or \ + (mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[-1].X and + mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[-1].Y and + mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[-1].Z): + + resultWires.append(mirrored) + else: + # Make sure that our mirrored edges meet up and are ordered properly + aEdges = w.wrapped.Edges + aEdges.extend(mirrored.wrapped.Edges) + comp = FreeCADPart.Compound(aEdges) + mirroredWire = comp.connectEdgesToWires(False).Wires[0] + + resultWires.append(cadquery.Shape.cast(mirroredWire)) return resultWires def _calcTransforms(self): """ - Computes transformation martrices to convert betwene local and global coordinates + Computes transformation martrices to convert between local and global coordinates """ #r is the forward transformation matrix from world to local coordinates #ok i will be really honest-- i cannot understand exactly why this works - #something bout the order of the transaltion and the rotation. - # the double-inverting is strange, and i dont understand it. + #something bout the order of the translation and the rotation. + # the double-inverting is strange, and I don't understand it. r = FreeCAD.Base.Matrix() #forward transform must rotate and adjust for origin