top of page

Maya | Python | Rigging | Hand Part 1

  • Writer: Max Liu
    Max Liu
  • Dec 19, 2024
  • 5 min read

  • Guide Bones Creation:

    • When the user clicks "Create Left hand guide figures bone," the script creates a guide joint hierarchy starting with L_Hand_gde_JNT.

    • For each finger (Thumb, Index, Middle, Ring, and Pinky), it creates four guide joints (e.g., L_Thumb1_gde_JNT to L_Thumb4_gde_JNT).

    • These guide joints are positioned in a rough formation, allowing the user to adjust them as needed before finalizing the rig.

  • Final Joints and Controls Creation:

    • When the user clicks "Create figures controls and a fist control," the script duplicates the guide joints, removes the gde part from their names, and creates a final joint hierarchy (e.g., L_Thumb1_JNT).

    • It then creates a wireframe cube control for each joint (except the hand joint itself) for easier selection and manipulation.

    • A special L_fist_CTL control is created at the position of L_Hand_JNT and given a fist attribute (0 to 1).

  • Fist Function Setup (Set Driven Keys):

    • The script sets driven keys so that adjusting the fist attribute on L_fist_CTL drives the rotation of the finger joints.

    • When fist = 0, the fingers are straight (0° rotation).

    • When fist = 1, the fingers curl (e.g., 50° rotation).

    • This allows an animator to open and close the hand by simply adjusting one attribute.

  • Reset fingers control transform:

    • Store the fingers controls while it is been created.

    • Reset the transfrom when click the button.


Code:

import maya.cmds as cmds

# Finger and joint naming details
fingers = ["Thumb", "Index", "Middle", "Ring", "Pinky"]
num_joints_per_finger = 4  # e.g. Thumb1, Thumb2, Thumb3, Thumb4

# Dictionary to store initial transforms of controls
initial_transforms = {}

def create_left_hand_guide_bones(*args):
    """
    Creates a left hand guide joint hierarchy with naming conventions:
    L_Hand_gde_JNT as root.
    For each finger in [Thumb, Index, Middle, Ring, Pinky], creates:
    L_{FingerName}1_gde_JNT ... L_{FingerName}4_gde_JNT

    These are positioned in a simple formation for the user to adjust.
    """
    # Check if guide joints already exist
    if cmds.objExists("L_Hand_gde_JNT"):
        cmds.warning("Guide joints seem to exist already.")
        return
    
    # Create the hand guide joint at origin
    hand_jnt = cmds.joint(name="L_Hand_gde_JNT", position=(0, 10, 0))
    cmds.select(clear=True)
    
    # Positioning offsets for each finger so user can see and adjust them
    base_x_positions = {
        "Thumb":  -2,
        "Index":  -1,
        "Middle":  0,
        "Ring":    1,
        "Pinky":   2
    }
    
    for f in fingers:
        # Start finger chain
        base_x = base_x_positions[f]
        # Create the first finger joint
        first_name = "L_{0}1_gde_JNT".format(f)
        first_jnt = cmds.joint(name=first_name, position=(base_x, 10, 1))
        cmds.parent(first_jnt, hand_jnt)
        
        # Create subsequent joints for the finger
        parent = first_jnt
        for i in range(2, num_joints_per_finger+1):
            cmds.select(clear=True)
            jnt_name = "L_{0}{1}_gde_JNT".format(f, i)
            jnt = cmds.joint(name=jnt_name, position=(base_x, 10, i+1))
            cmds.parent(jnt, parent)
            parent = jnt
    
    cmds.select(clear=True)
    cmds.inViewMessage(amg="Guide bones created.", pos="midCenter", fade=True)


def create_figures_controls_and_fist_control(*args):
    """
    1. Create final joints from guide joints (remove the '_gde_' part in name).
    2. Create a cube control for each final joint for easy selection.
    3. Create a L_fist_CTL aligned to L_Hand_JNT with a 'fist' attribute.
    4. Set Driven Keys so that 'fist' attribute controls the rotation of finger joints.
    """
    # Check if guide exists
    if not cmds.objExists("L_Hand_gde_JNT"):
        cmds.warning("No guide joints found. Please create guide bones first.")
        return
    
    hand_guide = "L_Hand_gde_JNT"
    all_guide_joints = cmds.listRelatives(hand_guide, allDescendents=True, type="joint") or []
    all_guide_joints.append(hand_guide)
    all_guide_joints = sorted(all_guide_joints, key=lambda x: len(x))
    
    old_to_new = {}
    cmds.select(clear=True)
    for jnt in all_guide_joints:
        new_name = jnt.replace("_gde_", "_")
        pos = cmds.xform(jnt, q=True, ws=True, t=True)
        new_jnt = cmds.joint(name=new_name, position=pos)
        rot = cmds.xform(jnt, q=True, ws=True, ro=True)
        cmds.xform(new_jnt, ws=True, ro=rot)
        old_to_new[jnt] = new_jnt
        cmds.select(clear=True)
    
    # Re-parent final joints
    for old_jnt, new_jnt in old_to_new.items():
        parent = cmds.listRelatives(old_jnt, parent=True, type="joint")
        if parent:
            new_parent = old_to_new.get(parent[0], None)
            if new_parent:
                cmds.parent(new_jnt, new_parent)
                
    # Create cube controls for each final joint (except the hand joint)
    control_hierarchy = {}
    for old_jnt, new_jnt in old_to_new.items():
        if new_jnt == "L_Hand_JNT":
            continue
        ctl_name = new_jnt.replace("_JNT", "_CTL")
        
        # Create a wireframe cube control
        points = [
            [-0.25, -0.25, -0.25], [0.25, -0.25, -0.25], [0.25, 0.25, -0.25], [-0.25, 0.25, -0.25], [-0.25, -0.25, -0.25],
            [-0.25, -0.25, 0.25], [0.25, -0.25, 0.25], [0.25, 0.25, 0.25], [-0.25, 0.25, 0.25], [-0.25, -0.25, 0.25],
            [0.25, -0.25, 0.25], [0.25, -0.25, -0.25], [0.25, 0.25, -0.25], [0.25, 0.25, 0.25], [-0.25, 0.25, 0.25], [-0.25, 0.25, -0.25]
        ]
        ctl = cmds.curve(name=ctl_name, degree=1, point=points)
        
        pos = cmds.xform(new_jnt, q=True, ws=True, t=True)
        rot = cmds.xform(new_jnt, q=True, ws=True, ro=True)
        cmds.xform(ctl, ws=True, t=pos, ro=rot)
        # Store the initial transform
        initial_transforms[ctl_name] = {'translate': pos, 'rotate': rot}
        
        # Parent constraint the joint to the control
        cmds.parentConstraint(ctl, new_jnt, mo=True)
        
        # Store the control in the hierarchy dictionary
        control_hierarchy[new_jnt] = ctl

    # Parent the controls to follow the joint hierarchy
    for old_jnt, new_jnt in old_to_new.items():
        if new_jnt == "L_Hand_JNT":
            continue
        parent = cmds.listRelatives(new_jnt, parent=True, type="joint")
        if parent:
            parent_ctl = control_hierarchy.get(parent[0], None)
            if parent_ctl:
                cmds.parent(control_hierarchy[new_jnt], parent_ctl)

    # Create the L_fist_CTL at the hand joint
    fist_ctl = cmds.circle(name="L_fist_CTL", normal=(0,1,0), radius=1)[0]
    hand_pos = cmds.xform("L_Hand_JNT", q=True, ws=True, t=True)
    cmds.xform(fist_ctl, ws=True, t=hand_pos)
    
    # Add the fist attribute
    if not cmds.attributeQuery("fist", node=fist_ctl, exists=True):
        cmds.addAttr(fist_ctl, longName="fist", attributeType="float", min=0, max=1, defaultValue=0, keyable=True)
    
    # Set Driven Keys for finger curl
    # At fist=0, all finger joints are straight (rotateX=0)
    # At fist=1, fingers curl (rotateX = e.g., -50 degrees)
    # Adjust values as desired for a nicer fist pose.
    
    # We'll identify the finger joints by their naming pattern:
    # L_Thumb1_JNT, L_Thumb2_JNT, ... L_Pinky4_JNT
    # They should be children of L_Hand_JNT
    all_final_joints = cmds.listRelatives("L_Hand_JNT", allDescendents=True, type="joint") or []
    # Filter out thumb and finger joints only (ignore the hand joint)
    finger_joints = [j for j in all_final_joints if any(f in j for f in fingers)]
    
    # Set the initial "fist" to 0 and rotate all finger joints to 0:
    cmds.setAttr(fist_ctl + ".fist", 0)
    for jnt in finger_joints:
        cmds.setAttr(jnt + ".rotateX", 0)
    cmds.setDrivenKeyframe([j + ".rotateX" for j in finger_joints], cd=fist_ctl + ".fist")

    # Now set "fist" to 1 and rotate finger joints to a curled pose:
    cmds.setAttr(fist_ctl + ".fist", 1)
    # We'll curl them more at the end joints. For simplicity, let's do a uniform rotation:
    # You can adjust these values per joint for a more natural curl.
    for jnt in finger_joints:
        # Example: rotateX = 50 for a curled look
        cmds.setAttr(jnt + ".rotateX", 50)
    cmds.setDrivenKeyframe([j + ".rotateX" for j in finger_joints], cd=fist_ctl + ".fist")

    # Return fist to 0
    cmds.setAttr(fist_ctl + ".fist", 0)

    # Set Driven Keys for controls to follow the joints
    for jnt in finger_joints:
        ctl = jnt.replace("_JNT", "_CTL")
        if cmds.objExists(ctl):
            cmds.setDrivenKeyframe([ctl + ".rotateX"], cd=fist_ctl + ".fist", dv=0, v=0)
            cmds.setDrivenKeyframe([ctl + ".rotateX"], cd=fist_ctl + ".fist", dv=1, v=50)

    cmds.inViewMessage(amg="Final joints and controls created with fist function.", pos="midCenter", fade=True)

def reset_control(finger):
    for i in range(1, num_joints_per_finger + 1):
        ctl_name = 'L_' + finger + str(i) + '_CTL'
        print('ctl_name:', ctl_name)
        if cmds.objExists(ctl_name) and ctl_name in initial_transforms:
            print('found ctl_name:', ctl_name)
            cmds.xform(ctl_name, ws=True, t=initial_transforms[ctl_name]['translate'])
            cmds.xform(ctl_name, ws=True, ro=initial_transforms[ctl_name]['rotate'])

def create_figure_ui():
    # If the window exists, delete it
    if cmds.window("figureUI", exists=True):
        cmds.deleteUI("figureUI")

    # Create a new window
    window = cmds.window("figureUI", title="Figure Creator", widthHeight=(300,200))
    
    # Create a layout
    cmds.columnLayout(adjustableColumn=True)
    
    # Add buttons
    cmds.button(label="Create Left hand guide figures bone", command=create_left_hand_guide_bones)
    cmds.separator(height=10)
    cmds.button(label="Create figures controls and a fist control", command=create_figures_controls_and_fist_control)
    cmds.separator(height=10)
    
    # Add reset buttons for each finger

    cmds.button(label="Reset {0}".format(fingers[0]), command=lambda f=fingers[0]: reset_control(fingers[0]))
    cmds.button(label="Reset {0}".format(fingers[1]), command=lambda f=fingers[1]: reset_control(fingers[1]))
    cmds.button(label="Reset {0}".format(fingers[2]), command=lambda f=fingers[2]: reset_control(fingers[2]))
    cmds.button(label="Reset {0}".format(fingers[3]), command=lambda f=fingers[3]: reset_control(fingers[3]))
    cmds.button(label="Reset {0}".format(fingers[4]), command=lambda f=fingers[4]: reset_control(fingers[4]))

    
    cmds.showWindow(window)

# To display the UI, run:
create_figure_ui()

Yorumlar


bottom of page