Tuesday, March 01, 2016

Leg Rigging in Maya

I wrote up this little leg rigging tutorial with the Python code I captured while manually rigging.  I thought this might be a handy resource for aspiring riggers.  This is basically the process I use when figuring out some new rigging technique.  Later on I will go back and refine the code to make it more concice and versatile.

IK Stretchy no Flip Leg Tutorial

In this tutorial I will walk you through the creation of a robust ik leg setup.  This is not the only way to build a leg, but I feel this setup introduces some pretty useful concepts that can be applied to your own rig.  Lets get started.

Step1:  Create the joints
First we need to draw our joint chain.  In my example I have named the joints ( jnt_pelvis, ikj_hip, ikj_knee, ikj_ankle, ikj_ball, and ikj_toe).  Once the joints are created, you should orient them using Skeleton/Orient Joint.  I chose to use X down the bone and Y up.  This orientation will be important later on, so just make a mental note if you decide to use a different orientation.

Step2: Drawing the IK:
Draw an ikRP solver from ikj_hip to ikj_ankle.
Draw an ikSC solver from ikj_ankle to ikj_ball.
Draw an ikSC solver from ikj_ball to ikj_toe.
Here is an example of how we could do this with Python.
cmds.ikHandle(n= "ikh_leg", sj= "ikj_hip", ee= "ikj_ankle", sol = "ikRPsolver")
cmds.ikHandle(n= "ikh_ball", sj= "ikj_ankle", ee= "ikj_ball", sol = "ikSCsolver")
cmds.ikHandle(n= "ikh_toe", sj= "ikj_ball", ee= "ikj_toe", sol = "ikSCsolver")

Step3:  Grouping the IK:
We need to create groups to use for rotating the toe, foot roll, and so on.  The setup I am presenting is a bit different than most tutorials I have seen, and we need to create some extra groups to accommodate that fact.
Create the following groups.  I will write this in Python format.

footGroups = ("grp_footPivot", "grp_heel", "grp_toe", "grp_ball", "grp_flap")
for item in footGroups:
cmds.group(n=item, empty=True, world=True)

grp_footPivot should be left at the origin.
grp_heel should be moved to the heel of the foot geometry.
grp_toe should be moved to ikj_toe.
grp_ball should be moved to ikj_ball.
grp_flap should be moved to ikj_ball
You can move these by first getting the position of the joints using cmds.xform
You can then place the groups with xform again.
hipPos = cmds.xform("ikj_hip", q=True, ws=True, t=True)
anklePos = cmds.xform("ikj_ankle", q=True, ws=True, t=True)
ballPos = cmds.xform("ikj_ball", q=True, ws=True, t=True)
toePos = cmds.xform("ikj_toe", q=True, ws=True, t=True)
cmds.xform("grp_toe", ws=True, t=toePos)
cmds.xform("grp_ball", ws=True, t=ballPos)
cmds.xform("grp_flap", ws=True, t=ballPos)
The heel is tricky. We need an object to set the position

Now we need to put our groups and ik handles into a hierarchy.  

cmds.parent('grp_heel', 'grp_footPivot')
cmds.parent('grp_toe', 'grp_heel')
cmds.parent('grp_ball', 'grp_toe')
cmds.parent('grp_flap', 'grp_toe')
cmds.parent('ikh_leg', 'grp_ball')
cmds.parent('ikh_ball', 'grp_ball')
cmds.parent('ikh_toe', 'grp_flap')
cmds.parent(‘grp_footPivot’, ‘ctrl_leg’)

Step 4:  Foot Control
Create a control object to control your foot.  Snap the controls pivot to your ikj_ankle and be sure to freeze transforms.  I named my foot control “ctrl_leg”.  Now you can parent the “grp_footPivot” to “ctrl_leg”

Step 5:  No Flip Knee
Create a locator. Name it “lctrPv_leg”, and snap it to the position of “jnt_pelvis”. Parent the locator under “jnt_pelvis”.  
pelvisPos = cmds.xform('jnt_pelvis', q=True, ws=True, t=True)
cmds.xform('lctrPv_leg', ws=True, t=pelvisPos)

Select the locator, then the “ikh_leg”, and execute Constrain > Pole Vector.
cmds.poleVectorConstraint ('lctrPv_leg', 'ikh_leg', weight=1)
Create a float attribute called "Twist" on the ikFootCtrl controller.  Select the ctrl_leg and execute this command.
cmds.addAttr( shortName='Twist', longName='Twist', defaultValue=0, k=True)

Create a plusMinusAverage utility, and call it pmaNode_LegTwist.
Create a multiplyDivide utility and call it mdNode_LegTwist.
cmds.shadingNode("plusMinusAverage", asUtility=True, n='pmaNode_LegTwist')
cmds.shadingNode("multiplyDivide", asUtility=True, n='mdNode_LegTwist')

Set up the connections using the following commands :
cmds.connectAttr('ctrl_leg.Twist', 'mdNode_LegTwist.input1X')
cmds.connectAttr('ctrl_leg.ry', 'mdNode_LegTwist.input1Y')
cmds.connectAttr('jnt_pelvis.ry', 'mdNode_LegTwist.input1Z')
cmds.setAttr('mdNode_LegTwist.input2X', -1)
cmds.setAttr('mdNode_LegTwist.input2Y', -1)
cmds.setAttr('mdNode_LegTwist.input2Z', -1)
cmds.connectAttr('mdNode_LegTwist.input1X', 'pmaNode_LegTwist.input1D[0]')
cmds.connectAttr('mdNode_LegTwist.input1Y', 'pmaNode_LegTwist.input1D[1]')
cmds.connectAttr('pmaNode_LegTwist.output1D', 'ikh_leg.twist')

Step 6:  Create the Stretchy IK
Start by creating all of the nodes we will need for the stretch.
cmds.shadingNode("addDoubleLinear", asUtility=True, n='adlNode_LegStretch')
cmds.shadingNode("clamp", asUtility=True, n='clampNode_LegStretch')
cmds.shadingNode("multiplyDivide", asUtility=True, n='mdNode_LegStretch')
cmds.shadingNode("multiplyDivide", asUtility=True, n='mdNode_KneeStretch')
cmds.shadingNode("multiplyDivide", asUtility=True, n='mdNode_AnkleStretch')

Add a “Stretch” attribute to ctrl_leg.
cmds.addAttr( shortName='Stretch', longName='Stretch', defaultValue=0, k=True)

Create a distance tool to measure the distance between our hip and ankle joints.
Use Create/Measure Tools/Distance Tool.
Snap one locator to ikj_hip and name it ‘lctrDis_hip’.  Parent this locator to ‘jnt_pelvis’
Snap the other locator to ikj_ankle and name it ‘lctrDis_ankle’.  Parent this locator to grp_heel’
hipPos = cmds.xform('ikj_hip', q=True, ws=True, t=True)
anklePos = cmds.xform('ikj_ankle', q=True, ws=True, t=True)
disDim = cmds.distanceDimension(sp=(hipPos), ep=(anklePos))

cmds.rename('distanceDimension1', 'disDimNode_legStretch')
cmds.rename('locator1', 'lctrDis_hip')
cmds.rename('locator2', 'lctrDis_ankle')
cmds.parent('lctrDis_hip', 'jnt_pelvis')
cmds.parent('lctrDis_ankle', 'grp_ball')

Next we will need to figure out the length of the leg when it is fully extended.  We could do this by moving the leg control until the leg is straight, and querying the distance tools distance attribute, but this is inaccurate and can not be scripted.  Instead we will use python to get the translateX values of our joints.  We will then add those values.

kneeLen = cmds.getAttr('ikj_knee.tx')
print kneeLen
ankleLen = cmds.getAttr('ikj_ankle.tx')
print ankleLen
legLen = (kneeLen + ankleLen)
print legLen

Enter our new found length values into the corresponding Nodes.
cmds.setAttr('adlNode_LegStretch.input2', legLen)
cmds.setAttr('mdNode_LegStretch.input2X', legLen)
cmds.setAttr('mdNode_KneeStretch.input2X', kneeLen)
cmds.setAttr('mdNode_AnkleStretch.input2X', ankleLen)

Connect the nodes to get the final stretch value that will be applied to our joints.

The clamp node lets us control the amount of stretch.
cmds.connectAttr('ctrl_leg.Stretch', 'adlNode_LegStretch.input1')
cmds.setAttr ("clampNode_LegStretch.minR", 12.800084)
cmds.setAttr ("mdNode_LegStretch.operation",  2)

Connect the distance dimension so we always know the current length of the leg.
cmds.connectAttr('disDimNode_legStretch.distance', 'clampNode_LegStretch.inputR')
cmds.connectAttr( 'adlNode_LegStretch.output', 'clampNode_LegStretch.maxR')

Now we feed the total value into a multiply divide so we can distribute the value to our joints.
cmds.connectAttr('clampNode_LegStretch.outputR', 'mdNode_LegStretch.input1X')
cmds.connectAttr('mdNode_LegStretch.outputX', 'mdNode_KneeStretch.input1X')
cmds.connectAttr('mdNode_LegStretch.outputX', 'mdNode_AnkleStretch.input1X')

Finally, we output our new values into the translateX of the knee and ankle joints.
cmds.connectAttr('mdNode_KneeStretch.outputX', 'ikj_knee.tx')
cmds.connectAttr('mdNode_AnkleStretch.outputX', 'ikj_ankle.tx')

Step 7:  Create the Foot Roll
By now you are hopefully familiar with what the lines of code are doing.  For this next part I will not offer too much in depth explanation.  We will create a foot roll attribute that goes all the way from the ball to the toe roll.  We will do this using only nodes.  It may be easier to accomplish this with set driven keys, but I prefer not to use SDKs when possible.

Create Attributes on the ctrl_foot
cmds.addAttr( shortName='Roll_Break', longName='Roll_Break', defaultValue=0, k=True)
cmds.addAttr( shortName='Foot_Roll', longName='Foot_Roll', defaultValue=0, k=True)
Setup the foot roll
Create utility nodes
cmds.shadingNode("condition", asUtility=True, n='conNode_ballRoll')
cmds.shadingNode("condition", asUtility=True, n='conNode_negBallRoll')
cmds.shadingNode("condition", asUtility=True, n='conNode_toeRoll')
cmds.shadingNode("plusMinusAverage", asUtility=True, n='pmaNode_ballRoll')
cmds.shadingNode("plusMinusAverage", asUtility=True, n='pmaNode_toeRoll')
cmds.shadingNode("condition", asUtility=True, n='conNode_heelRoll')
cmds.setAttr('pmaNode_toeRoll.operation', 2)
cmds.setAttr ("conNode_toeRoll.operation", 2)
cmds.setAttr ("conNode_toeRoll.colorIfFalseR", 0)
cmds.setAttr ("conNode_toeRoll.colorIfFalseG", 0)
cmds.setAttr ("conNode_toeRoll.colorIfFalseB", 0)
cmds.setAttr ('conNode_heelRoll.operation', 4)
cmds.setAttr('conNode_heelRoll.colorIfFalseB', 0)
cmds.setAttr('conNode_heelRoll.colorIfFalseR', 0)
cmds.setAttr('conNode_heelRoll.colorIfFalseG', 0)
cmds.setAttr("pmaNode_ballRoll.operation", 2)
cmds.setAttr ("conNode_negBallRoll.operation", 3)
cmds.setAttr ("conNode_ballRoll.operation", 3)

Setup Toe
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_toeRoll.firstTerm')
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_toeRoll.colorIfTrueR')
cmds.connectAttr('ctrl_leg.Roll_Break', 'conNode_toeRoll.secondTerm')
cmds.connectAttr('ctrl_leg.Roll_Break', 'conNode_toeRoll.colorIfFalseR')
cmds.connectAttr('ctrl_leg.Roll_Break', 'pmaNode_toeRoll.input1D[1]')
cmds.connectAttr('conNode_toeRoll.outColorR', 'pmaNode_toeRoll.input1D[0]')
cmds.connectAttr('pmaNode_toeRoll.output1D', 'grp_toe.rx')

Setup Heel
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_heelRoll.firstTerm')
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_heelRoll.colorIfTrueR')
cmds.connectAttr('conNode_heelRoll.outColorR', 'grp_heel.rotateX')
Setup Ball
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_ballRoll.firstTerm')
cmds.connectAttr('ctrl_leg.Foot_Roll', 'conNode_ballRoll.colorIfTrueR')
cmds.connectAttr('ctrl_leg.Roll_Break', 'conNode_negBallRoll.secondTerm')
cmds.connectAttr('ctrl_leg.Roll_Break', 'conNode_negBallRoll.colorIfTrueR')
cmds.connectAttr('conNode_negBallRoll.outColorR', 'pmaNode_ballRoll.input1D[0]')
cmds.connectAttr('grp_toe.rx', 'pmaNode_ballRoll.input1D[1]')
cmds.connectAttr('pmaNode_ballRoll.output1D', 'grp_ball.rx')
cmds.connectAttr('conNode_ballRoll.outColorR', 'conNode_negBallRoll.firstTerm')
cmds.connectAttr('conNode_ballRoll.outColorR', 'conNode_negBallRoll.colorIfFalseR')

Make a Toe Flap
We will need to make a new attribute called Toe_Flap on ctrl_foot.  Then we can connect Toe_Flap to grp_flap rotateX.
cmds.addAttr( shortName='Toe_Flap', longName='Toe_Flap', defaultValue=0, k=True)
cmds.connectAttr('ctrl_leg.Toe_Flap', 'grp_flap.rx')

Step 8:  Pivot for Bank and Twist
Create a new control object.  I am using something that looks like a pin.  Name the control ‘ctrl_footPivot’
Move the control to the grp_ball.
ballPos = cmds.xform('grp_ball', q=True, t=True, ws=True)
cmds.xform('ctrl_footPivot', t=ballPos)

Deselect the ctrl_footPivot, and create an empty group at the origin.  Name the group ‘grp_ctrl_footPivot’.
cmds.group(n='grp_ctrl_footPivot', empty=True)
Parent the grp_ctrl_footPivot  to  ctrl_footPivot.
cmds.parent('grp_ctrl_footPivot', 'ctrl_footPivot')
Parent the ctrl_footPivot to ctrl_foot and freeze transforms on ctrl_footPivot.
cmds.parent('ctrl_footPivot', ‘ctrl_foot’)
cmds.makeIdentity( apply=True )

Now we will connect the grp_ctrl_footPivot.translate to grp_footPivot.rotatePivot
cmds.connectAttr('grp_ctrl_footPivot.translate', 'grp_footPivot.rotatePivot')
Move grp_ctrl_footPivot to the position of grp_ball.
cmds.xform('grp_ctrl_footPivot', t=ballPos)

Make a couple more attributes for twist and bank, then hook those up to the grp_footPivot.
cmds.addAttr( shortName='Foot_Pivot', longName='Foot_Pivot', defaultValue=0, k=True)
cmds.addAttr( shortName='Foot_Bank', longName='Foot_Bank', defaultValue=0, k=True)
cmds.connectAttr('ctrl_leg.Foot_Pivot', 'grp_footPivot.ry')
cmds.connectAttr('ctrl_leg.Foot_Bank', 'grp_footPivot.rz')

Please email me with any questions or errors.  I do not claim that this is the perfect leg rig.  I designed this to try some new ideas and to incorporate some time tested methods.
-Ryan Griffin    ryan@griffinanimation.com

No comments: