top of page

Maya | Python | Rigging | Hand Part 2

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

Updated: Dec 27, 2024


Update note:

  1. Added a mirror function create_right_hand_guide_bones(*args) to let users mirror finished left-hand joints across the Z axis.

  2. Modified create_figures_controls_and_fist_control to handle both L_ and R_ joints by checking the existence of each side’s prefix before applying controls.

  3. Created rename_selected_prefix to rename selected objects from "L_" to "R_".

// ...existing code...

def rename_selected_prefix(*args):

    selection = cmds.ls(selection=True)

    for obj in selection:

        if obj.startswith("L_"):

            new_name = "R_" + obj[2:]

            cmds.rename(objnew_name)

// ...existing code...

def create_right_hand_guide_bones(*args):

    left_joints = cmds.ls('L_*_JNT'type='joint')

    if left_joints:

        cmds.select(left_joints[0]hi=True)

        cmds.mirrorJoint(left_joints[0]mirrorXY=TruemirrorBehavior=TruesearchReplace=('L_''R_'))

        cmds.inViewMessage(amg="Right hand mirror created."pos="midCenter"fade=True)

    else:

        cmds.warning("No left hand joints found to mirror.")

// ...existing code...

def create_figures_controls_and_fist_control():

    for prefix in ["L_""R_"]:

        # Check prefix existence, then create controls as needed

        joints = cmds.ls(prefix + "*_gde_JNT"type='joint')

        if not joints:

            cmds.warning("No joints found for prefix: {}".format(prefix))

            continue

        # ...existing control creation code (apply to each prefix)...

        cmds.inViewMessage(amg="Fist controls created for {} hand.".format(prefix)pos="midCenter"fade=True)

// ...existing code...



Expand Full Code

from locale import normalize
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_right_hand_guide_bones(*args):
    # Assume left-hand guide joints are named with 'L_' prefix
    left_joints = cmds.ls('L_*_JNT', type='joint')
    if left_joints:
        # Select the left-hand root joint and its hierarchy
        cmds.select(left_joints[0], hi=True)
        # Mirror joints across the Z axis, replacing 'L_' with 'R_'
        cmds.mirrorJoint(left_joints[0], mirrorXY=True, mirrorBehavior=True, searchReplace=('L_', 'R_'))
        cmds.inViewMessage(amg="Right hand guide joints created.", pos="midCenter", fade=True)
    else:
        cmds.warning("Left hand guide joints not found.")

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 and R_fist_CTL aligned to L_Hand_JNT and R_Hand_JNT with a 'fist' attribute.
    4. Set Driven Keys so that 'fist' attribute controls the rotation of finger joints.
    """
    for prefix in ["L_", "R_"]:
        hand_guide = "{}Hand_gde_JNT".format(prefix)
        if not cmds.objExists(hand_guide):
            cmds.warning("No guide joints found for {} hand. Please create guide bones first.".format(prefix))
            continue
        
        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 == "{}Hand_JNT".format(prefix):
                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 == "{}Hand_JNT".format(prefix):
                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)
        
        # Calculate the normal direction of the hand joint from hand joint to middle finger
        hand_pos = cmds.xform("{}Hand_gde_JNT".format(prefix), q=True, ws=True, t=True)
        middle_pos = cmds.xform("{}Middle1_gde_JNT".format(prefix), q=True, ws=True, t=True)
        direction = [middle_pos[i] - hand_pos[i] for i in range(3)]

        # Create the fist control at the hand joint
        fist_ctl = cmds.circle(name="{}fist_CTL".format(prefix), normal=direction, radius=1)[0]
        hand_pos = cmds.xform("{}Hand_JNT".format(prefix), q=True, ws=True, t=True)
        cmds.xform(fist_ctl, ws=True, t=hand_pos)
        cmds.parentConstraint(fist_ctl, "{}Hand_JNT".format(prefix), mo=True)

        # 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("{}Hand_JNT".format(prefix), 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)]

        # 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 for {} hand.".format(prefix), pos="midCenter", fade=True)

def reset_control(finger):
    for i in range(1, num_joints_per_finger + 1):
        for prefix in ["L_", "R_"]:
            ctl_name = "{}{}{}_CTL".format(prefix, finger, i)
            print(ctl_name)
            if cmds.objExists(ctl_name) and ctl_name in initial_transforms:
                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,800))
    
    # 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 Right hand guide figures bone", command=create_right_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)

    cmds.separator(height=5)
    cmds.button(label="Reset {0}".format(fingers[0]), command=lambda *args: reset_control(fingers[0]))
    cmds.button(label="Reset {0}".format(fingers[1]), command=lambda *args: reset_control(fingers[1]))
    cmds.button(label="Reset {0}".format(fingers[2]), command=lambda *args: reset_control(fingers[2]))
    cmds.button(label="Reset {0}".format(fingers[3]), command=lambda *args: reset_control(fingers[3]))
    cmds.button(label="Reset {0}".format(fingers[4]), command=lambda *args: reset_control(fingers[4]))
    #-------------------------------------------------------------
    # Create button for rename_prefix_l_to_r
    cmds.separator(height=10)
    cmds.button(label="Rename selected L to R", command=rename_prefix_l_to_r)
    
    cmds.showWindow(window)

def rename_prefix_l_to_r(*args):
    # Get the currently selected objects
    selected = cmds.ls(selection=True)
    if not selected:
        cmds.warning("No objects selected.")
        return
    
    for obj in selected:
        # Check if name starts with L_
        print(obj)
        if obj.startswith("L_"):
            cmds.warning("Object name starts with L_")
            # Create new name by replacing L_ with R_
            new_name = "R_" + obj[2:]
            # Rename the object
            cmds.rename(obj, new_name)

# To display the UI, run:
create_figure_ui()







Comments


bottom of page