Keywords: Maya, Rig Control Curve, Control Rig, Layered Controls, Animation Layers

Maya Rigging series of articles:

Summary

Why we need the Layered Controls? two reasons:

  • We want to prevent the animator from touching the skeleton, because we may add or remove joints in the future, but we don’t expect animator rework the animation.
  • If we add other nodes(Group, Curve, Locator, etc.) directly into skeleton, these nodes alter the hierarchy of the scene, and it will cause issues when it comes to export to the other engine (Unreal Engine, Unity ect.).

So we need a top layer to drive joints, that’s Layered Controls!

There’re 3 ways to create controller.

1st way: Parent Constraints

Take shoulder joint as example.

1, Create Curve: Create -> NURBS Primitives -> Circle

2, Scale the curve.

3, Freeze the transfrom and delete history:

  • Modify -> Freeze Transformations.
  • Edit -> Delete by Type -> History. (Alt + Shift + D)

  • Freeze Transformations of Control Curve is the required steps before moving it!!!
    Because we need to zero the translation and rotation, and normalize the scale of Control, otherwise the transformations of controllers will affect the transformations of joints, e.g. When driving the joint’s Offset Parent Matrix using controller’s transformations in subsequent flow, if the scale of controller had not beed normalized, joints will get big.
  • Delete History of Control is also the required steps before moving it, otherwise it will lead to translation issue when doing Match Pivots afterward.

Q: How to address the transformation issue of controller if forgot to Freeze Transformations before seting up hierarchy?
A: Unparent the controller from the hierarchy to leave it in world space coordinate, Freeze Transformations and Delete History, then restore it to the previous hierarchy.

4, Match Transforms.
Select shoulder joint and curve together, Modify -> Match Transformations -> Match All Transforms.

Another way to snap Control to Joint: Hold V and drag Control to target joint.

5, Tweak the curve’s rotation.

6, Match Pivots.

As shown in following picture, the pivot orientation of controller and joint wasn’t match, this will lead to rotation issue when driving joints from controller afterward.

Solution: Modify -> Match Transformations -> Math Pivots (Check Orientation)。
Result:

7, Parent Constraints.
Select controller(curve) first, and then select shoulder joint: Hold Space -> Constrain -> Parent.

Now you can drive shoulder joint using controler.

More control curve templates: Control Icons

You will see a constraint node was created under the target joint after executing Parent Constraints.

and the Translate and Rotate attributes have been flagged with blue brick, which means these attributes cannot be changed directly.

2nd way: Parent and Merge Node

1, Create a curve and tweak the transform.

Which axis is modified depends on the target joint coordinate axis.

2, Freeze the transfrom and delete history:

  • Modify -> Freeze Transformations.
  • Edit -> Delete by Type -> History. (Alt + Shift + D)

3, Display the transform node: Right click hierarchy viewport -> Shapes.

4, Parent the curve to the target joint:

parent -r -s

5, Delete curve’s transform node, and hide the Shapes, now you can drive the joint using curve which likes Handle.

3rd way: Offset Parent Matrix

1, Create a Control named ctrl_arm_l.

2, Move the Control to the target joint. Don't forget to freeze the transfromation and delete history before moving.

3, Offset parent matrix using the script that mentioned in follows content (see: Transform Offset Method 2nd: Offset Parent Matrix).

Result:

Offset Parent Matrix for controller isn’t the required step to current example, but for better production specification to the subsequent flow, it’s recommended to do that.

4, Create an empty Group named arm_grp_l.

5, Select Group first, then select Control, Modify -> Match Transformations -> Match All Transforms.

Result:

6, Offset parent matrix to the Group.

It caused transformation issue to joints if move the joints into group before Match Transformations and Offset Parent Matrix.

7, Open Node Editor.

8, Select the Control and Group, then click Add Selected Node.

Then connect the OffsetParentMatrix between Control and Group.

9, Move the joint into the group.

You should Offset Parent Matrix of group before moving joint into the group, otherwise you will not be able to Offset Parent Matrix afterwards.

Now the joint can be driven by Control.

Parent Constraints vs. Offset Parent Matrix

Maybe you wonder what’s difference between Parent Constraints and Offset Parent Matrix.

Difference:
Animation that driven by Offset Parent Matrix will not bake out transform curve of joints, so if want to import animation into Unreal or Unity, you should create controller using Constraints.

Controller Tweaking

Change the display color:

Change the line width:

Hide Joint for Controler

In the process of rigging, we can hide the joints to highlight the controllers: Show -> Viewport -> Uncheck Joints.

But it causes to hide controllers when the visibility of joint was off.

Q: As mentioned before, to prevent animator from touching skeleton, so we need to hide the joint, is there any way to display the controllers while joint was hidden?
A: Change the joint’s draw sytle.

We can change only one joint at a time on GUI, but can do it in batch using script.

Hide all joints:

string $target = "root";
select -hi $target;
string $joints[] = `ls -sl`;
for($joint in $joints)
{
	setAttr ($joint + ".drawStyle") 2;
	//print($joint + "\n");
}

Show all joints:

//change drawStyle from 2 to 0
setAttr ($joint + ".drawStyle") 0;
Transform Offset Method 1st: Group

As the Create Controller Method 1st say, when you tweaked the transform of controller, Translate and Rotation are non-Zero, thus it’s not friendly for animator.

Solution:
1, Create an empty Group. (shortcut: Ctrl + G)

2, Select Group first, then select Controller, then Match All Transforms.

Then you can see the Group’s transform changed.

3, Drag the Controller into Group, then you can see the Controller’s transform was zeroed.

Transform Offset Method 2nd: Offset Parent Matrix

Take Create Controller Method 1st as an example again.

From Maya 2020, it provide a new feature named Offset Parent Matrix to offset transform.
You can just copy the transform from Transform Attributes to Offset Parent Matrix, then zero the transform from Transform Attributes.

Now you can found that the controller’s transform was zeroed.

Script to copy Offset Parent Matirx and zero Transformation (credits to Muream):

import maya.api.OpenMaya as om
import maya.cmds as cmds

TRANSFORM_NODETYPES = ["transform", "joint"]

def has_non_default_locked_attributes(node):
    locked_attributes = []
    for attribute in ["translate", "rotate", "scale", "jointOrient"]:
        default_value = 1 if attribute == "scale" else 0
        for axis in "XYZ":
            if cmds.attributeQuery(attribute + axis, node=node, exists=True):
                attribute_name = "{}.{}{}".format(node, attribute, axis)
                current_value = cmds.getAttr(attribute_name)
                if cmds.getAttr(attribute_name, lock=True) and current_value != default_value:
                    return True

def reset_transforms(node):
    for attribute in ["translate", "rotate", "scale", "jointOrient"]:
        value = 1 if attribute == "scale" else 0
        for axis in "XYZ":
            if cmds.attributeQuery(attribute + axis, node=node, exists=True):
                attribute_name = "{}.{}{}".format(node, attribute, axis)
                if not cmds.getAttr(attribute_name, lock=True):
                    cmds.setAttr(attribute_name, value)

def bake_transform_to_offset_parent_matrix(node):
    if cmds.nodeType(node) not in TRANSFORM_NODETYPES:
        raise ValueError("Node {} is not a transform node".format(node))

    if has_non_default_locked_attributes(node):
        raise RuntimeError("Node {} has at least one non default locked attribute(s)".format(node))

    local_matrix = om.MMatrix(cmds.xform(node, q=True, m=True, ws=False))
    offset_parent_matrix = om.MMatrix(cmds.getAttr(node + ".offsetParentMatrix"))
    baked_matrix = local_matrix * offset_parent_matrix
    cmds.setAttr(node + ".offsetParentMatrix", baked_matrix, type="matrix")

    reset_transforms(node)

def bake_transform_to_offset_parent_matrix_selection():
    for node in cmds.ls(sl=True):
        bake_transform_to_offset_parent_matrix(node)

if __name__ == "__main__":
    bake_transform_to_offset_parent_matrix_selection()

Q: why don’t we zero transform offset using Freeze Transformations before Parent Constraints?
A: Freeze Transformations will change the axis direction of pivot.

You can also create a custom button in shelf to run this script. See: Shelf custom button

References

Character Rigging for Beginners: 03 Primary Controls
https://www.youtube.com/watch?v=m37fhS9MNSI&list=PLbvsJz5ZcmxHEPiw_kF3vHjR023rIjR05&index=3

Character Rigging for Beginners: 04 Secondary Controls
https://www.youtube.com/watch?v=tsOXYOoGBNY&list=PLbvsJz5ZcmxHEPiw_kF3vHjR023rIjR05&index=4

Realtime Creature Rigging Workshop (7 / 19) : Control creation
https://www.youtube.com/watch?v=6jO9jQQfSL8

Plugins

Maya control curve UI
https://peerke.gumroad.com/l/WRtX


紫光阁名录:多拉尔·海兰察
海兰察(满语:ᡥᠠᡳ᠌ᠯᠠᠨᠴᠠ,穆麟德转写:Hailanca;1739—1793年),多拉尔氏,索伦部(今鄂温克族)人,后入满洲镶黄旗,世居黑龙江,生于呼伦贝尔阿荣旗霍尔奇镇,乾隆中后期名将,因屡立战功,官至正一品领侍卫内大臣,爵封一等超勇公;卒谥武壮。清朝惯例,阵亡者方能入祀昭忠祠,乾隆御准海兰察加恩入祀,旌扬战功。