Working With Quaternions in Maya With Python and OpenMaya
Quaternions are a very effective way of setting orientations or performing rotations, but if you want to do that using Python, you’re going to need to leverage the OpenMaya API.
Euler or Quaternions?#
Most of the time, when working with rotations in Maya, you’re going to be working with Euler (pronounced oiler) angles. They’re intuitive to work with, allow for rotations greater than 360 degrees, and provide greater interpolation control between keyframes because of the ability to manipulate keyframe tangents.
Sometimes, you’ll need to use quaternions though. Especially when exporting out of Maya to other applications that prefer quaternions or working with other APIs. Quaterions have their benefits too. Quaternions allow for smooth rotations from one orientation to another using the shortest rotation. Thus, they don’t suffer from gimbal locking or flipping.
Getting to Work#
To work with quaternions in Maya using Python, you’ll need to use the
OpenMaya API. Most of the work is done with MFnTransform
and
MQuaternion
. Let’s start out by getting a quaternion that represents
a transform node’s current orientation:
import maya.cmds
import maya.api.OpenMaya as OpenMaya
# create a cube to work with for this example.
transform_node, shape_node = maya.cmds.polyCube()
# let's rotate it a bit
maya.cmds.setAttr(transform_node+".rotateY", 60)
maya.cmds.setAttr(transform_node+".rotateX", 45)
# create an MSelectionList
sel_list = OpenMaya.MSelectionList()
# pass in the name of the xform node we want the quaternion for
sel_list.add(transform_node)
# get the MObject for the xform node
obj = sel_list.getDependNode(0)
# get access to the transform node
xform = OpenMaya.MFnTransform(obj)
# get the rotation as an MQuaternion and store it
quat = xform.rotation(asQuaternion=True)
# normalize the quaternion in place. you generally want a normalized quaternion.
quat.normalizeIt()
# quat is still an MQuaternion object. I convert it to a 4 item list
# to make it easier to work with. [x,y,z,w]
py_quat = [quat[x] for x in range(4)]
Setting a Transform’s Orientation Using Quaternions#
If you want to set the orientation of a transform node using a
quaternion, you can use MFnTransform.setRotation
or
MFnTransform.setRotationComponents
. The former takes an
MQuaternion
object as a parameter and the latter can take sequence
of numbers representing x, y, z, w:
'''Continuing on from the example of how to get a quaternion...'''
# I can reset my cube's orientation with an identity quaternion
# which corresponds to "no rotation" or (0,0,0,1)
xform.setRotation(OpenMaya.MQuaternion.kIdentity, OpenMaya.MSpace.kObject )
# I can rotate it back to the rotation I set with getAttr
xform.setRotation(quat, om.MSpace.kObject)
# Or if I want to use my python list, I can use this method.
xform.setRotationComponents(py_quat, om.MSpace.kObject, asQuaternion=True)
Relative Rotations Using Quaternions#
If you want to perform a relative rotation instead of a complete
orientation update, you can use MFnTransform.rotateBy
:
# I'll use the math module to convert degrees to radians
import math
# create a quaternion with an angle of rotation and an axis of rotation
q = OpenMaya.MQuaternion( math.radians(10), OpenMaya.MVector(0,1,0) )
# This will rotate my object to 10 degrees about the world Y axis
xform.rotateBy(q, OpenMaya.MSpace.kTransform)
Lastly, quaternions can be used to interpolate between two rotations
using a technique called, Spherical Linear Interpolation (slerp) and the
Maya Python API provides a simple function to accomplish that,
MQuaternion.slerp
.
'''Continuing on from the previous examples...'''
# This gives me a quaternion for halfway between identity and the
# rotation I set with maya.cmds
half = OpenMaya.MQuaternion.slerp(OpenMaya.MQuaternion.kIdentity, quat, 0.5)
# test it out to confirm
xform.setRotation(half, OpenMaya.MSpace.kObject)
You can take slerp a step further to animate the rotation.
'''Continuing on from the previous examples...'''
# use time to sleep and observe the animation. otherwise, it happens too fast.
import time
# reset the rotation
xform.setRotation(OpenMaya.MQuaternion.kIdentity, OpenMaya.MSpace.kObject )
# grab a quaternion for a tenth of the rotation
step = OpenMaya.MQuaternion.slerp(OpenMaya.MQuaternion.kIdentity, quat, 0.1)
# rotate by that step 10 times
for x in range(10):
xform.rotateBy(step, OpenMaya.MSpace.kObject)
# force the 3D view to redraw
maya.cmds.refresh()
time.sleep(0.25)