diff --git a/cadquery/cq.py b/cadquery/cq.py index 59a1180..7293813 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -742,6 +742,19 @@ def rotate(self, axisStartPoint, axisEndPoint, angleDegrees): return self.newObject([o.rotate(axisStartPoint, axisEndPoint, angleDegrees) for o in self.objects]) + def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)): + """ + Mirror a single CQ object. This operation is the same as in the FreeCAD PartWB's mirroring + + :param mirrorPlane: the plane to mirror about + :type mirrorPlane: string, one of "XY", "YX", "XZ", "ZX", "YZ", "ZY" the planes + :param basePointVector: the base point to mirror about + :type basePointVector: tuple + """ + newS = self.newObject([self.objects[0].mirror(mirrorPlane, basePointVector)]) + return newS.first() + + def translate(self, vec): """ Returns a copy of all of the items on the stack moved by the specified translation vector. diff --git a/cadquery/freecad_impl/shapes.py b/cadquery/freecad_impl/shapes.py index 76af1c1..0ceae22 100644 --- a/cadquery/freecad_impl/shapes.py +++ b/cadquery/freecad_impl/shapes.py @@ -184,9 +184,23 @@ def isEqual(self, other): def isValid(self): return self.wrapped.isValid() - def BoundingBox(self): + def BoundingBox(self, tolerance=0.1): + self.wrapped.tessellate(tolerance) return BoundBox(self.wrapped.BoundBox) + def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)): + if mirrorPlane == "XY" or mirrorPlane== "YX": + mirrorPlaneNormalVector = FreeCAD.Base.Vector(0, 0, 1) + elif mirrorPlane == "XZ" or mirrorPlane == "ZX": + mirrorPlaneNormalVector = FreeCAD.Base.Vector(0, 1, 0) + elif mirrorPlane == "YZ" or mirrorPlane == "ZY": + mirrorPlaneNormalVector = FreeCAD.Base.Vector(1, 0, 0) + + if type(basePointVector) == tuple: + basePointVector = Vector(basePointVector) + + return Shape.cast(self.wrapped.mirror(basePointVector.wrapped, mirrorPlaneNormalVector)) + def Center(self): # A Part.Shape object doesn't have the CenterOfMass function, but it's wrapped Solid(s) does if isinstance(self.wrapped, FreeCADPart.Shape): diff --git a/doc/examples.rst b/doc/examples.rst index 0705da5..06c79aa 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -312,6 +312,64 @@ introduce horizontal and vertical lines, which make for slightly easier coding. * :py:meth:`Workplane` * :py:meth:`Workplane.extrude` +Mirroring 3D Objects +----------------------------- + +.. cq_plot:: + + result0 = (cadquery.Workplane("XY") + .moveTo(10,0) + .lineTo(5,0) + .threePointArc((3.9393,0.4393),(3.5,1.5)) + .threePointArc((3.0607,2.5607),(2,3)) + .lineTo(1.5,3) + .threePointArc((0.4393,3.4393),(0,4.5)) + .lineTo(0,13.5) + .threePointArc((0.4393,14.5607),(1.5,15)) + .lineTo(28,15) + .lineTo(28,13.5) + .lineTo(24,13.5) + .lineTo(24,11.5) + .lineTo(27,11.5) + .lineTo(27,10) + .lineTo(22,10) + .lineTo(22,13.2) + .lineTo(14.5,13.2) + .lineTo(14.5,10) + .lineTo(12.5,10 ) + .lineTo(12.5,13.2) + .lineTo(5.5,13.2) + .lineTo(5.5,2) + .threePointArc((5.793,1.293),(6.5,1)) + .lineTo(10,1) + .close()) + result = result0.extrude(100) + + result = result.rotate((0, 0, 0),(1, 0, 0), 90) + + result = result.translate(result.val().BoundingBox().center.multiply(-1)) + + mirXY_neg = result.mirror(mirrorPlane="XY", basePointVector=(0, 0, -30)) + mirXY_pos = result.mirror(mirrorPlane="XY", basePointVector=(0, 0, 30)) + mirZY_neg = result.mirror(mirrorPlane="ZY", basePointVector=(-30,0,0)) + mirZY_pos = result.mirror(mirrorPlane="ZY", basePointVector=(30,0,0)) + + result = result.union(mirXY_neg).union(mirXY_pos).union(mirZY_neg).union(mirZY_pos) + + build_object(result) + +.. topic:: Api References + + .. hlist:: + :columns: 2 + + * :py:meth:`Workplane.moveTo` + * :py:meth:`Workplane.lineTo` + * :py:meth:`Workplane.threePointArc` + * :py:meth:`Workplane.extrude` + * :py:meth:`Workplane.mirror` + * :py:meth:`Workplane.union` + * :py:meth:`CQ.rotate` Creating Workplanes on Faces ----------------------------- diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index 01e7753..f8b9b2a 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -550,6 +550,43 @@ def testCut(self): self.assertEqual(10,currentS.faces().size()) + def testBoundingBox(self): + """ + Tests the boudingbox center of a model + """ + result0 = (Workplane("XY") + .moveTo(10,0) + .lineTo(5,0) + .threePointArc((3.9393,0.4393),(3.5,1.5)) + .threePointArc((3.0607,2.5607),(2,3)) + .lineTo(1.5,3) + .threePointArc((0.4393,3.4393),(0,4.5)) + .lineTo(0,13.5) + .threePointArc((0.4393,14.5607),(1.5,15)) + .lineTo(28,15) + .lineTo(28,13.5) + .lineTo(24,13.5) + .lineTo(24,11.5) + .lineTo(27,11.5) + .lineTo(27,10) + .lineTo(22,10) + .lineTo(22,13.2) + .lineTo(14.5,13.2) + .lineTo(14.5,10) + .lineTo(12.5,10 ) + .lineTo(12.5,13.2) + .lineTo(5.5,13.2) + .lineTo(5.5,2) + .threePointArc((5.793,1.293),(6.5,1)) + .lineTo(10,1) + .close()) + result = result0.extrude(100) + bb_center = result.val().BoundingBox().center + self.saveModel(result) + self.assertAlmostEqual(14.0, bb_center.x, 3) + self.assertAlmostEqual(7.5, bb_center.y, 3) + self.assertAlmostEqual(50.0, bb_center.z, 3) + def testCutThroughAll(self): """ Tests a model that uses more than one workplane