Maya | Python | Rigging | Hand Part 2
- Max Liu
- Dec 24, 2024
- 5 min read
Updated: Dec 27, 2024
Update note:
Added a mirror function create_right_hand_guide_bones(*args) to let users mirror finished left-hand joints across the Z axis.
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.
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(obj, new_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=True, mirrorBehavior=True, searchReplace=('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