Skip to content

Instantly share code, notes, and snippets.

@rondreas
Last active July 23, 2024 16:44
Show Gist options
  • 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