Skip to content

Instantly share code, notes, and snippets.

@rondreas
Last active June 8, 2021 21:49
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rondreas/1c6d4e5fc6535649780d5b65fc5a9283 to your computer and use it in GitHub Desktop.
Save rondreas/1c6d4e5fc6535649780d5b65fc5a9283 to your computer and use it in GitHub Desktop.
Script to mirror transform similar to Maya's Joint Mirror Tool.
import pymel.core as pm
def xformMirror(transforms=[], across='YZ', behaviour=True):
""" Mirrors transform across hyperplane.
transforms -- list of Transform or string.
across -- plane which to mirror across.
behaviour -- bool
"""
# No specified transforms, so will get selection
if not transforms:
transforms = pm.selected(type='transform')
# Check to see all provided objects is an instance of pymel transform node,
elif not all(map(lambda x: isinstance(x, pm.nt.Transform), transforms)):
raise ValueError("Passed node which wasn't of type: Transform")
# Validate plane which to mirror across,
if not across in ('XY', 'YZ', 'XZ'):
raise ValueError("Keyword Argument: 'across' not of accepted value ('XY', 'YZ', 'XZ').")
for transform in transforms:
# Get the worldspace matrix, as a list of 16 float values
mtx = pm.xform(transform, q=True, ws=True, m=True)
# Invert rotation columns,
rx = [n * -1 for n in mtx[0:9:4]]
ry = [n * -1 for n in mtx[1:10:4]]
rz = [n * -1 for n in mtx[2:11:4]]
# Invert translation row,
t = [n * -1 for n in mtx[12:15]]
# Set matrix based on given plane, and whether to include behaviour or not.
if across is 'XY':
mtx[14] = t[2] # set inverse of the Z translation
# Set inverse of all rotation columns but for the one we've set translate to.
if behaviour:
mtx[0:9:4] = rx
mtx[1:10:4] = ry
elif across is 'YZ':
mtx[12] = t[0] # set inverse of the X translation
if behaviour:
mtx[1:10:4] = ry
mtx[2:11:4] = rz
else:
mtx[13] = t[1] # set inverse of the Y translation
if behaviour:
mtx[0:9:4] = rx
mtx[2:11:4] = rz
# Finally set matrix for transform,
pm.xform(transform, ws=True, m=mtx)
@mikemalinowski
Copy link

mikemalinowski commented May 2, 2019

This is great - thanks for sharing. The only suggestion i would make is to declare a dictionary to store the transforms, then apply the transforms in a second pass... so replacing line 60 with this....

    stored_matrices[transform] = mtx

for transform in transforms:
    pm.xform(transform, ws=True, m=stored_matrices[transform])

As the only issue i have found is selecting a hierarchy of joints, its reading the transforms whilst its setting them which can cause issues - which caching the transforms first means you're always reading the right worldspace transform

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment