In rigging land, and I’m making a rotation driver from built-in maya nodes. This usually drives some front of chain blendshapes (use Chad Vernon’s awesome cvShapeInverter tool) to do corrective blends. This is similar in concept to the Comet poseDeformer.

The guts of it is very simple – we want to store and match a rotation. So, we multiply a vector (usually [1,0,0] for a joint) by the object’s matrix. This gives us a vector of the input object’s rotation. Dot product the object’s live rotation with the initial stored vector which gives us a 0-1 value, where 1 means the two vectors are pointing the same way. Run it though a range node to adjust it’s falloff, and use that to drive something.

Sufficed to say, this would all be pretty damn easy in Softimage/ICE (with the extra benefit of being able to do all sorts of awesome blendshape voodoo), but I’ve gotta deal with Maya for the moment.

So, here’s some code.

import maya.cmds as cmds

def poseDriver(inObject, namePrefix):
    """
    Bodgy, node-based pose driver. Unlike the elegance of cometPoseDeformer and such.
    
    Uses some simple vectors and dot products and shit to see if two rotations match.
    
    Adjust the setRange node oldMin value to affect the range of the poseDriver.
    
    
    @param inObject: Object to read rotaions from
    @type inObject: String
    @param namePrefix: Unique(ish) name to attach to all nodes in this system
    @type namePrefix: String
    """
    
    nodesColl = []
    
    matrixMult = cmds.createNode('pointMatrixMult', name=namePrefix + '_matrixMult')
    nodesColl.append(matrixMult)
    cmds.setAttr(matrixMult + '.vectorMultiply', 1)
    
    # this axis needs to correspond to the down axis of the bone
    # generally X
    cmds.setAttr(matrixMult + '.inPoint', 1, 0, 0, type="double3")
    
    cmds.connectAttr(inObject + '.matrix', matrixMult + '.inMatrix', force=True)
    
    
    vectorMult = cmds.createNode('vectorProduct', name=namePrefix + '_vectorMult')
    nodesColl.append(vectorMult)
    cmds.setAttr(vectorMult + '.normalizeOutput', 1)
    
    
    cmds.connectAttr(matrixMult + '.output', vectorMult + '.input1', force=True)
    cmds.setAttr(vectorMult + '.input2X', cmds.getAttr(matrixMult + '.outputX'))
    cmds.setAttr(vectorMult + '.input2Y', cmds.getAttr(matrixMult + '.outputY'))
    cmds.setAttr(vectorMult + '.input2Z', cmds.getAttr(matrixMult + '.outputZ'))

    
    rangeNode = cmds.createNode('setRange', name=namePrefix + '_range')
    nodesColl.append(rangeNode)
    cmds.connectAttr(vectorMult + '.output', rangeNode + '.value', force=True)
    cmds.setAttr(rangeNode + '.oldMin', 0.5, 0.5, 0.5, type="double3")
    cmds.setAttr(rangeNode + '.oldMax', 1, 1, 1, type="double3")
    cmds.setAttr(rangeNode + '.max', 1, 1, 1, type="double3")
    
    # hide the nodes from the channel box
    for node in nodesColl:
        cmds.setAttr(node + '.isHistoricallyInteresting', False)
    
    return rangeNode + '.outValueX'

Use the (namePrefix)_range.outValueX attribute to drive a blendshape.