Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to mirror from a selected face #526

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions cadquery/cq.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,17 +1021,38 @@ def rotate(
]
)

def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)):
def mirror(self, mirrorPlane: Union[Literal["XY", ...], VectorLike, Face] = "XY", basePointVector=None,
union=False):
"""
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
or the normal vector of the plane eg (1,0,0)
or a Face object
:param basePointVector: the base point to mirror about (this is overwritten if a Face is passed)
:type basePointVector: tuple
"""
:param union: If true will perform a union operation on the mirrored object
:type union: bool
"""
print(type(mirrorPlane))
if type(mirrorPlane) == Workplane or type(mirrorPlane) == Face:
if type(mirrorPlane) == Workplane:
mirrorPlane = mirrorPlane.objects[0]
# now mirrorPlane is a face object
if basePointVector is None:
basePointVector = mirrorPlane.Center()
else:
basePointVector = Vector(basePointVector)
mirrorPlane = mirrorPlane.normalAt(basePointVector)
else:
if basePointVector is None:
basePointVector = Vector(0, 0, 0)
newS = self.newObject([self.objects[0].mirror(mirrorPlane, basePointVector)])
return newS.first()
if union:
return self.union(newS.first())
else:
return newS.first()

def translate(self, vec: VectorLike) -> "Workplane":
"""
Expand Down
21 changes: 13 additions & 8 deletions cadquery/occ_impl/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,16 +466,21 @@ def BoundingBox(

def mirror(
self,
mirrorPlane=Literal["XY", "YX", "XZ", "ZX", "YZ", "ZY"],
mirrorPlane: Union[Literal["XY", "YX", "XZ", "ZX", "YZ", "ZY"], VectorLike] = "XY",
basePointVector: VectorLike = (0, 0, 0),
) -> "Shape":

if mirrorPlane == "XY" or mirrorPlane == "YX":
mirrorPlaneNormalVector = gp_Dir(0, 0, 1)
elif mirrorPlane == "XZ" or mirrorPlane == "ZX":
mirrorPlaneNormalVector = gp_Dir(0, 1, 0)
elif mirrorPlane == "YZ" or mirrorPlane == "ZY":
mirrorPlaneNormalVector = gp_Dir(1, 0, 0)
if type(mirrorPlane) == str:
if mirrorPlane == "XY" or mirrorPlane == "YX":
mirrorPlaneNormalVector = gp_Dir(0, 0, 1)
elif mirrorPlane == "XZ" or mirrorPlane == "ZX":
mirrorPlaneNormalVector = gp_Dir(0, 1, 0)
elif mirrorPlane == "YZ" or mirrorPlane == "ZY":
mirrorPlaneNormalVector = gp_Dir(1, 0, 0)
else:
if isinstance(mirrorPlane, tuple):
mirrorPlaneNormalVector = gp_Dir(*mirrorPlane)
else:
mirrorPlaneNormalVector = gp_Dir(*mirrorPlane.toTuple())

if isinstance(basePointVector, tuple):
basePointVector = Vector(basePointVector)
Expand Down
30 changes: 30 additions & 0 deletions doc/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,36 @@ Mirroring 3D Objects
* :py:meth:`Workplane.union`
* :py:meth:`Workplane.rotate`


Mirroring from faces
-----------------------------

.. cadquery::

This example shows how you can mirror about a selected face. It also shows how the resulting mirrored object can be unioned immediately with the referenced mirror geometry.

r = (cq.Workplane("XY")
.line(0, 1)
.line(1, 0)
.line(0, -.5)
.close()
.extrude(1))
mme
r = r.mirror(r.faces(">X"), union=True)


.. topic:: Api References

.. hlist::
:columns: 2

* :py:meth:`Workplane.line`
* :py:meth:`Workplane.close`
* :py:meth:`Workplane.extrude`
* :py:meth:`Workplane.faces`
* :py:meth:`Workplane.mirror`
* :py:meth:`Workplane.union`

Creating Workplanes on Faces
-----------------------------

Expand Down
72 changes: 72 additions & 0 deletions tests/test_workplanes.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,75 @@ def testZYPlaneBasics(self):
self.assertTupleAlmostEquals(p.zDir.toTuple(), xInvAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)

def test_mirror(self):
"""Create a unit box and mirror it so that it doubles in size"""
b2 = Workplane().box(1, 1, 1)
b2 = b2.mirror("XY", (0, 0, .5), union=True)
bbBox = b2.findSolid().BoundingBox()
assert ([bbBox.xlen, bbBox.ylen, bbBox.zlen] == [1., 1., 2])

def test_mirror_axis(self):
"""Create a unit box and mirror it so that it doubles in size"""
b2 = Workplane().box(1, 1, 1)
b2 = b2.mirror((0, 0, 1), (0, 0, .5), union=True)
bbBox = b2.findSolid().BoundingBox()
assert ([bbBox.xlen, bbBox.ylen, bbBox.zlen] == [1., 1., 2])

def test_mirror_workplane(self):
"""Create a unit box and mirror it so that it doubles in size"""
b2 = Workplane().box(1, 1, 1)

# double in Z plane
b2 = b2.mirror(b2.faces(">Z"), union=True)
bbBox = b2.findSolid().BoundingBox()
assert ([bbBox.xlen, bbBox.ylen, bbBox.zlen] == [1., 1., 2])

# double in Y plane
b2 = b2.mirror(b2.faces(">Y"), union=True)
bbBox = b2.findSolid().BoundingBox()
assert ([bbBox.xlen, bbBox.ylen, bbBox.zlen] == [1., 2., 2])

# double in X plane
b2 = b2.mirror(b2.faces(">X"), union=True)
bbBox = b2.findSolid().BoundingBox()
assert ([bbBox.xlen, bbBox.ylen, bbBox.zlen] == [2., 2., 2])

def test_mirror_equivalence(self):
"""test that the a plane string, plane normal and face object perform a mirror operation in the same way"""
boxes = []
boxDims = 1
for i in range(3): # create 3 sets of identical boxes
boxTmp = Workplane("XY").box(boxDims, boxDims, boxDims)
boxTmp = boxTmp.translate([i * 2, 0, boxDims / 2])
boxes.append(boxTmp)

# 3 different types of plane definition
planeArg = ["XY", (0, 0, 1), boxes[0].faces("<Z")]
planeOffset = (0, 0, 0.5) # use the safe offset for each
boxResults = [] # store the resulting mirrored objects
for b, p in zip(boxes, planeArg):
boxResults.append(b.mirror(p, planeOffset, union=True))

# all resulting boxes should be equal to each other
for i in range(len(boxResults) - 1):
curBoxDims = boxResults[i].findSolid().BoundingBox() # get bbox dims
nextBoxDims = boxResults[i].findSolid().BoundingBox() # get bbox dims
cbd = (curBoxDims.xlen, curBoxDims.ylen, curBoxDims.zlen)
nbd = (nextBoxDims.xlen, nextBoxDims.ylen, nextBoxDims.zlen)
self.assertTupleAlmostEquals(cbd, nbd, 4)

def test_mirror_face(self):
"""Create a triangle and mirror into a unit box"""
r = (Workplane("XY")
.line(0, 1)
.line(1, -1)
.close()
.extrude(1))

bbBox = r.findSolid().BoundingBox()
self.assertTupleAlmostEquals((bbBox.xlen, bbBox.ylen, bbBox.zlen), (1., 1., 1.), 4)

r = r.mirror(r.faces().objects[1], union=True)
bbBox = r.findSolid().BoundingBox()
self.assertTupleAlmostEquals((bbBox.xlen, bbBox.ylen, bbBox.zlen), (1., 1., 1.), 4)