Update
This commit is contained in:
603
2023/scripts/animation_tools/ikfk_switch.py
Normal file
603
2023/scripts/animation_tools/ikfk_switch.py
Normal file
@@ -0,0 +1,603 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
import math
|
||||
import sys
|
||||
import inspect
|
||||
import importlib
|
||||
|
||||
# Crucial for fixing namespace issues:
|
||||
# Dynamically gets the module name (e.g., 'animation_tools.ikfk_switch')
|
||||
|
||||
MODULE_NAME = __name__
|
||||
|
||||
# ==============================================================================
|
||||
# UI Creation
|
||||
# ==============================================================================
|
||||
|
||||
def IKFK_Switch_UI():
|
||||
"""
|
||||
Creates the main IK/FK switch Maya window interface.
|
||||
"""
|
||||
WINDOW_NAME = "IKFK_Switch_UI"
|
||||
WINDOW_TITLE = "IK/FK Switch V2.9 (Final Button Fix)"
|
||||
WINDOW_WIDTH = 300
|
||||
|
||||
# Check if window exists and delete it
|
||||
if cmds.window(WINDOW_NAME, exists=True):
|
||||
cmds.deleteUI(WINDOW_NAME, window=True)
|
||||
|
||||
# Create window
|
||||
cmds.window(WINDOW_NAME, width=WINDOW_WIDTH, title=WINDOW_TITLE)
|
||||
|
||||
# Main Layout
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=1)
|
||||
|
||||
# --- Helper function for button commands ---
|
||||
# CORE FIX: Removed the 'python("...")' wrapper.
|
||||
# The function now returns a pure Python command string for button execution.
|
||||
def py_cmd(func_name, args=""):
|
||||
# Format: 'import module; module.function("args")' (Pure Python string)
|
||||
if args:
|
||||
# Use double quotes to avoid escaping issues
|
||||
return 'import {0}; {0}.{1}("{2}")'.format(MODULE_NAME, func_name, args)
|
||||
else:
|
||||
return 'import {0}; {0}.{1}()'.format(MODULE_NAME, func_name)
|
||||
|
||||
# --- Edit Section ---
|
||||
cmds.frameLayout(label="Edit", collapse=True, collapsable=True,
|
||||
collapseCommand="cmds.window('{}', edit=True, height=578)".format(WINDOW_NAME),
|
||||
expandCommand="cmds.window('{}', edit=True, height=578)".format(WINDOW_NAME))
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=2)
|
||||
cmds.button(label="<<< ADV Build >>>", command=py_cmd("sg_ADV_Build"))
|
||||
cmds.button(label="<<< Empty >>>", command=py_cmd("sg_Empty"))
|
||||
cmds.setParent("..")
|
||||
cmds.setParent("..")
|
||||
|
||||
# --- Load FK Ctrl Section ---
|
||||
cmds.frameLayout(label="Load FK Ctrl")
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=2)
|
||||
|
||||
# FK Joint Root
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_01", text="", placeholderText="FK Joint Root")
|
||||
cmds.button(label="< FK Joint Root", command=py_cmd("sg_setTextField", "target_01"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# FK Joint Mid
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_02", text="", placeholderText="FK Joint Mid")
|
||||
cmds.button(label="< FK Joint Mid", command=py_cmd("sg_setTextField", "target_02"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# FK Joint End
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_03", text="", placeholderText="FK Joint End")
|
||||
cmds.button(label="< FK Joint End", command=py_cmd("sg_setTextField", "target_03"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.separator(style="in", height=5)
|
||||
|
||||
# FK Ctrl Root
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_04", text="", placeholderText="FK Ctrl Root")
|
||||
cmds.button(label="< FK Ctrl Root", command=py_cmd("sg_setTextField", "target_04"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# FK Ctrl Mid
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_05", text="", placeholderText="FK Ctrl Mid")
|
||||
cmds.button(label="< FK Ctrl Mid", command=py_cmd("sg_setTextField", "target_05"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# FK Ctrl End
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_06", text="", placeholderText="FK Ctrl End")
|
||||
cmds.button(label="< FK Ctrl End", command=py_cmd("sg_setTextField", "target_06"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.setParent("..")
|
||||
cmds.setParent("..")
|
||||
|
||||
# --- Load IK Ctrl Section ---
|
||||
cmds.frameLayout(label="Load IK Ctrl")
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=2)
|
||||
|
||||
# IK Joint Root
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_07", text="", placeholderText="IK Joint Root")
|
||||
cmds.button(label="< IK Joint Root", command=py_cmd("sg_setTextField", "target_07"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# IK Joint Mid
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_08", text="", placeholderText="IK Joint Mid")
|
||||
cmds.button(label="< IK Joint Mid", command=py_cmd("sg_setTextField", "target_08"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# IK Joint End
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_09", text="", placeholderText="IK Joint End")
|
||||
cmds.button(label="< IK Joint End", command=py_cmd("sg_setTextField", "target_09"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.separator(style="in", height=5)
|
||||
|
||||
# IK Ctrl Root
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_10", text="", placeholderText="IK Ctrl Root")
|
||||
cmds.button(label="< IK Ctrl Root", command=py_cmd("sg_setTextField", "target_10"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# IK Ctrl Pole
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_11", text="", placeholderText="IK Ctrl Pole")
|
||||
cmds.button(label="< IK Ctrl Pole", command=py_cmd("sg_setTextField", "target_11"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.setParent("..")
|
||||
cmds.setParent("..")
|
||||
|
||||
# --- Load Switch Ctrl Section ---
|
||||
cmds.frameLayout(label="Load Switch Ctrl")
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=2)
|
||||
|
||||
# Switch Ctrl
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_12", text="", placeholderText="Switch Ctrl")
|
||||
cmds.button(label="< Switch Ctrl", command=py_cmd("sg_setTextField", "target_12"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# Switch Attr
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_13", text="", placeholderText="Switch Attr Name")
|
||||
cmds.button(label="< Switch Attr", command=py_cmd("sg_setLoadAttr"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.setParent("..")
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.separator(style="in", height=5)
|
||||
|
||||
# Build Button
|
||||
cmds.button(height=32, label="<<< Build Seamless Switching >>>", command=py_cmd("sg_Execute"))
|
||||
|
||||
cmds.setParent("..")
|
||||
|
||||
# Adjust window height and show
|
||||
cmds.window(WINDOW_NAME, edit=True, height=578)
|
||||
cmds.showWindow(WINDOW_NAME)
|
||||
|
||||
|
||||
def sg_setTextField(target):
|
||||
"""
|
||||
Sets the name of the currently selected object into the specified text field.
|
||||
"""
|
||||
selection = cmds.ls(selection=True, long=False)
|
||||
|
||||
if selection:
|
||||
selected_object = selection[0]
|
||||
cmds.textField(target, edit=True, text=selected_object)
|
||||
else:
|
||||
cmds.warning("Please select an object.")
|
||||
|
||||
|
||||
def sg_setLoadAttr():
|
||||
"""
|
||||
Sets the name of the selected attribute in the Channel Box into the target_13 text field.
|
||||
"""
|
||||
# Query selected attributes in the Channel Box
|
||||
attrs = cmds.channelBox("mainChannelBox", query=True, selectedMainAttributes=True)
|
||||
|
||||
if not attrs:
|
||||
cmds.error("Select an attribute in the Channel Box...")
|
||||
return
|
||||
|
||||
# Take only the first selected attribute
|
||||
attribute_name = attrs[0]
|
||||
cmds.textField("target_13", edit=True, text=attribute_name)
|
||||
|
||||
|
||||
def sg_Execute():
|
||||
"""
|
||||
Reads all values from UI controls and calls the sg_SeamlessSwitching function.
|
||||
"""
|
||||
# Get values from UI controls
|
||||
FK_Joint_Root = cmds.textField("target_01", query=True, text=True)
|
||||
FK_Joint_Mid = cmds.textField("target_02", query=True, text=True)
|
||||
FK_Joint_End = cmds.textField("target_03", query=True, text=True)
|
||||
FK_Ctrl_Root = cmds.textField("target_04", query=True, text=True)
|
||||
FK_Ctrl_Mid = cmds.textField("target_05", query=True, text=True)
|
||||
FK_Ctrl_End = cmds.textField("target_06", query=True, text=True)
|
||||
IK_Joint_Root = cmds.textField("target_07", query=True, text=True)
|
||||
IK_Joint_Mid = cmds.textField("target_08", query=True, text=True)
|
||||
IK_Joint_End = cmds.textField("target_09", query=True, text=True)
|
||||
IK_Ctrl_Root = cmds.textField("target_10", query=True, text=True)
|
||||
IK_Ctrl_Pole = cmds.textField("target_11", query=True, text=True)
|
||||
Switch_Ctrl = cmds.textField("target_12", query=True, text=True)
|
||||
Switch_Attr = cmds.textField("target_13", query=True, text=True)
|
||||
|
||||
# Check for empty values
|
||||
all_targets = [FK_Joint_Root, FK_Joint_Mid, FK_Joint_End, FK_Ctrl_Root, FK_Ctrl_Mid,
|
||||
FK_Ctrl_End, IK_Joint_Root, IK_Joint_Mid, IK_Joint_End, IK_Ctrl_Root,
|
||||
IK_Ctrl_Pole, Switch_Ctrl, Switch_Attr]
|
||||
|
||||
for target in all_targets:
|
||||
if not target:
|
||||
cmds.error("All fields must be filled before building the switch.")
|
||||
return
|
||||
|
||||
# Call core build function
|
||||
sg_SeamlessSwitching(
|
||||
FK_Joint_Root,
|
||||
FK_Joint_Mid,
|
||||
FK_Joint_End,
|
||||
FK_Ctrl_Root,
|
||||
FK_Ctrl_Mid,
|
||||
FK_Ctrl_End,
|
||||
IK_Joint_Root,
|
||||
IK_Joint_Mid,
|
||||
IK_Joint_End,
|
||||
IK_Ctrl_Root,
|
||||
IK_Ctrl_Pole,
|
||||
Switch_Ctrl,
|
||||
Switch_Attr
|
||||
)
|
||||
|
||||
|
||||
def sg_ADV_Build():
|
||||
"""
|
||||
Builds IK/FK switching for sample limbs using preset naming conventions.
|
||||
NOTE: Modify names here to match your Rig naming convention.
|
||||
"""
|
||||
|
||||
# Right Arm
|
||||
sg_SeamlessSwitching(
|
||||
"FKXShoulder_R", "FKXElbow_R", "FKXWrist_R",
|
||||
"FKShoulder_R", "FKElbow_R", "FKWrist_R",
|
||||
"IKXShoulder_R", "IKXElbow_R", "IKXWrist_R",
|
||||
"IKArm_R", "PoleArm_R", "FKIKArm_R", "fkik"
|
||||
)
|
||||
|
||||
# Left Arm
|
||||
sg_SeamlessSwitching(
|
||||
"FKXShoulder_L", "FKXElbow_L", "FKXWrist_L",
|
||||
"FKShoulder_L", "FKElbow_L", "FKWrist_L",
|
||||
"IKXShoulder_L", "IKXElbow_L", "IKXWrist_L",
|
||||
"IKArm_L", "PoleArm_L", "FKIKArm_L", "fkik"
|
||||
)
|
||||
|
||||
# Right Leg
|
||||
sg_SeamlessSwitching(
|
||||
"FKXHip_R", "FKXKnee_R", "FKXAnkle_R",
|
||||
"FKHip_R", "FKKnee_R", "FKAnkle_R",
|
||||
"IKXHip_R", "IKXKnee_R", "IKXAnkle_R",
|
||||
"IKLeg_R", "PoleLeg_R", "FKIKLeg_R", "fkik"
|
||||
)
|
||||
|
||||
# Left Leg
|
||||
sg_SeamlessSwitching(
|
||||
"FKXHip_L", "FKXKnee_L", "FKXAnkle_L",
|
||||
"FKHip_L", "FKKnee_L", "FKAnkle_L",
|
||||
"IKXHip_L", "IKXKnee_L", "IKXAnkle_L",
|
||||
"IKLeg_L", "PoleLeg_L", "FKIKLeg_L", "fkik"
|
||||
)
|
||||
|
||||
def sg_Empty():
|
||||
"""
|
||||
Clears all text fields in the UI.
|
||||
"""
|
||||
for i in range(1, 14):
|
||||
target_name = "target_{:02d}".format(i)
|
||||
if cmds.textField(target_name, exists=True):
|
||||
cmds.textField(target_name, edit=True, text="")
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Core IK/FK Switching Logic
|
||||
# ==============================================================================
|
||||
|
||||
def sg_switching(locator_name):
|
||||
"""
|
||||
Executes the core IK/FK switching logic (seamless match).
|
||||
This function is called at runtime by the scriptJob.
|
||||
|
||||
Args:
|
||||
locator_name (str): The name of the Locator storing connection info.
|
||||
"""
|
||||
|
||||
# Get the Switch Control and attribute name (stored as string attributes)
|
||||
try:
|
||||
switch_ctrl = cmds.getAttr("{}.Switch_Ctrl".format(locator_name))
|
||||
switch_attr_name = cmds.getAttr("{}.Switch_Attr".format(locator_name))
|
||||
except:
|
||||
cmds.error("Locator {} does not have required attributes.".format(locator_name))
|
||||
return
|
||||
|
||||
# State check: Use optionVar to store the last state, preventing redundant execution
|
||||
option_var_name = locator_name
|
||||
current_state = cmds.getAttr("{}.IKFK_Seamless".format(switch_ctrl))
|
||||
|
||||
try:
|
||||
last_state = cmds.optionVar(query=option_var_name)
|
||||
except RuntimeError:
|
||||
last_state = -1
|
||||
|
||||
if last_state == current_state:
|
||||
# State has not changed, exit
|
||||
return
|
||||
|
||||
# Store current selection
|
||||
selection = cmds.ls(selection=True, long=False)
|
||||
|
||||
# Get all object names from the Locator (stored as string attributes)
|
||||
try:
|
||||
FK_Joint_Root = cmds.getAttr("{}.FK_Joint_Root".format(locator_name))
|
||||
FK_Joint_Mid = cmds.getAttr("{}.FK_Joint_Mid".format(locator_name))
|
||||
FK_Joint_End = cmds.getAttr("{}.FK_Joint_End".format(locator_name))
|
||||
FK_Ctrl_Root = cmds.getAttr("{}.FK_Ctrl_Root".format(locator_name))
|
||||
FK_Ctrl_Mid = cmds.getAttr("{}.FK_Ctrl_Mid".format(locator_name))
|
||||
FK_Ctrl_End = cmds.getAttr("{}.FK_Ctrl_End".format(locator_name))
|
||||
IK_Joint_Root = cmds.getAttr("{}.IK_Joint_Root".format(locator_name))
|
||||
IK_Joint_Mid = cmds.getAttr("{}.IK_Joint_Mid".format(locator_name))
|
||||
IK_Joint_End = cmds.getAttr("{}.IK_Joint_End".format(locator_name))
|
||||
IK_Ctrl_Root = cmds.getAttr("{}.IK_Ctrl_Root".format(locator_name))
|
||||
IK_Ctrl_Pole = cmds.getAttr("{}.IK_Ctrl_Pole".format(locator_name))
|
||||
except Exception as e:
|
||||
cmds.error("Failed to retrieve object names from Locator {}: {}".format(locator_name, e))
|
||||
return
|
||||
|
||||
# Get Min/Max values for the blend attribute
|
||||
try:
|
||||
attr_min = cmds.attributeQuery(switch_attr_name, node=switch_ctrl, minimum=True)
|
||||
attr_max = cmds.attributeQuery(switch_attr_name, node=switch_ctrl, maximum=True)
|
||||
min_val = attr_min[0] if attr_min else 0.0
|
||||
max_val = attr_max[0] if attr_max else 1.0
|
||||
except Exception:
|
||||
cmds.warning("Could not query min/max values for blend attribute. Defaulting to 0 and 1.")
|
||||
min_val = 0.0
|
||||
max_val = 1.0
|
||||
|
||||
current_time = cmds.currentTime(query=True)
|
||||
|
||||
# Set keyframes on all controls before switching (MEL lines 504-508)
|
||||
cmds.setKeyframe(FK_Ctrl_Root)
|
||||
cmds.setKeyframe(FK_Ctrl_Mid)
|
||||
cmds.setKeyframe(FK_Ctrl_End)
|
||||
cmds.setKeyframe(IK_Ctrl_Root)
|
||||
cmds.setKeyframe(IK_Ctrl_Pole)
|
||||
|
||||
# **MATCHING LOGIC**
|
||||
if current_state == 0: # Switch to IK (IKFK_Seamless: IK)
|
||||
|
||||
# 1. Match IK Ctrl Root (End Controller) - Using temp groups to preserve rotation offset
|
||||
# This matches the MEL version's approach (lines 510-519)
|
||||
tempGroup_A = cmds.group(empty=True)
|
||||
tempGroup_B = cmds.group(empty=True)
|
||||
|
||||
# Step 1: Match both temp groups to IK_Joint_End
|
||||
cmds.delete(cmds.parentConstraint(IK_Joint_End, tempGroup_A, weight=1))
|
||||
cmds.delete(cmds.parentConstraint(IK_Joint_End, tempGroup_B, weight=1))
|
||||
|
||||
# Step 2: Apply IK_Ctrl_Root's current orientation to tempGroup_B (preserve rotation offset)
|
||||
cmds.delete(cmds.orientConstraint(IK_Ctrl_Root, tempGroup_B, offset=(0, 0, 0), weight=1))
|
||||
|
||||
# Step 3: Parent tempGroup_B under tempGroup_A to create hierarchy
|
||||
cmds.parent(tempGroup_B, tempGroup_A)
|
||||
|
||||
# Step 4: Match tempGroup_A to FK_Joint_End (this moves the hierarchy)
|
||||
cmds.delete(cmds.parentConstraint(FK_Joint_End, tempGroup_A, weight=1))
|
||||
|
||||
# Step 5: Apply tempGroup_B's final transform to IK_Ctrl_Root
|
||||
con_A = cmds.parentConstraint(tempGroup_B, IK_Ctrl_Root, weight=1)
|
||||
cmds.setKeyframe(IK_Ctrl_Root)
|
||||
cmds.delete(con_A)
|
||||
|
||||
# 2. Calculate Pole Vector position based on FK chain bend direction
|
||||
# Get world space positions of FK joints
|
||||
pos_root = cmds.xform(FK_Joint_Root, query=True, worldSpace=True, translation=True)
|
||||
pos_mid = cmds.xform(FK_Joint_Mid, query=True, worldSpace=True, translation=True)
|
||||
pos_end = cmds.xform(FK_Joint_End, query=True, worldSpace=True, translation=True)
|
||||
|
||||
# Calculate the bend direction using vector projection
|
||||
# Vector from root to end (main chain direction)
|
||||
vec_RE = [pos_end[i] - pos_root[i] for i in range(3)]
|
||||
# Vector from root to mid
|
||||
vec_RM = [pos_mid[i] - pos_root[i] for i in range(3)]
|
||||
|
||||
# Length of main chain vector
|
||||
len_RE = math.sqrt(sum(vec_RE[i]**2 for i in range(3)))
|
||||
|
||||
if len_RE > 0.0001: # Avoid division by zero
|
||||
# Normalize the main chain vector
|
||||
norm_RE = [vec_RE[i] / len_RE for i in range(3)]
|
||||
|
||||
# Project vec_RM onto vec_RE to find the projection point
|
||||
proj_scalar = sum(vec_RM[i] * norm_RE[i] for i in range(3))
|
||||
proj_vec = [proj_scalar * norm_RE[i] for i in range(3)]
|
||||
|
||||
# Perpendicular vector (from projection point to mid joint)
|
||||
vec_perp = [vec_RM[i] - proj_vec[i] for i in range(3)]
|
||||
len_perp = math.sqrt(sum(vec_perp[i]**2 for i in range(3)))
|
||||
|
||||
if len_perp > 0.0001: # Chain has a bend
|
||||
# Normalize perpendicular vector
|
||||
norm_perp = [vec_perp[i] / len_perp for i in range(3)]
|
||||
|
||||
# Extension distance: use chain length as reference
|
||||
# Multiply by a factor to place pole vector at a reasonable distance
|
||||
extend_dist = len_RE * 0.5
|
||||
|
||||
# Pole Vector position = mid joint + perpendicular direction * extension distance
|
||||
pole_pos = [pos_mid[i] + norm_perp[i] * extend_dist for i in range(3)]
|
||||
|
||||
else: # Chain is straight, use a default offset
|
||||
# If straight, offset along Z-axis (or another appropriate axis)
|
||||
pole_pos = [pos_mid[0], pos_mid[1], pos_mid[2] + len_RE * 0.5]
|
||||
else:
|
||||
# Fallback: keep current position if chain length is zero
|
||||
pole_pos = cmds.xform(IK_Ctrl_Pole, query=True, worldSpace=True, translation=True)
|
||||
|
||||
# Apply calculated position to Pole Vector
|
||||
cmds.xform(IK_Ctrl_Pole, worldSpace=True, translation=pole_pos)
|
||||
cmds.setKeyframe(IK_Ctrl_Pole)
|
||||
|
||||
# Clean up temp groups
|
||||
cmds.delete(tempGroup_A)
|
||||
|
||||
# 3. Switch attribute: from FK (min_val) to IK (max_val)
|
||||
cmds.setAttr("{}.{}".format(switch_ctrl, switch_attr_name), min_val)
|
||||
cmds.setKeyframe(switch_ctrl, attribute=switch_attr_name, time=current_time - 1)
|
||||
cmds.setAttr("{}.{}".format(switch_ctrl, switch_attr_name), max_val)
|
||||
cmds.setKeyframe(switch_ctrl, attribute=switch_attr_name, time=current_time)
|
||||
|
||||
|
||||
elif current_state == 1: # Switch to FK (IKFK_Seamless: FK)
|
||||
|
||||
# Match FK controls to IK joints (MEL lines 530-538)
|
||||
# Use weight=1 instead of maintainOffset to match MEL behavior
|
||||
|
||||
# 1. Match FK Ctrl Root to IK Joint Root
|
||||
con_A = cmds.parentConstraint(IK_Joint_Root, FK_Ctrl_Root, weight=1)
|
||||
cmds.setKeyframe(FK_Ctrl_Root)
|
||||
cmds.delete(con_A)
|
||||
|
||||
# 2. Match FK Ctrl Mid to IK Joint Mid
|
||||
con_B = cmds.parentConstraint(IK_Joint_Mid, FK_Ctrl_Mid, weight=1)
|
||||
cmds.setKeyframe(FK_Ctrl_Mid)
|
||||
cmds.delete(con_B)
|
||||
|
||||
# 3. Match FK Ctrl End to IK Joint End
|
||||
con_C = cmds.parentConstraint(IK_Joint_End, FK_Ctrl_End, weight=1)
|
||||
cmds.setKeyframe(FK_Ctrl_End)
|
||||
cmds.delete(con_C)
|
||||
|
||||
# 4. Switch attribute: from IK (max_val) to FK (min_val)
|
||||
cmds.setAttr("{}.{}".format(switch_ctrl, switch_attr_name), max_val)
|
||||
cmds.setKeyframe(switch_ctrl, attribute=switch_attr_name, time=current_time - 1)
|
||||
cmds.setAttr("{}.{}".format(switch_ctrl, switch_attr_name), min_val)
|
||||
cmds.setKeyframe(switch_ctrl, attribute=switch_attr_name, time=current_time)
|
||||
|
||||
# Update optionVar
|
||||
cmds.optionVar(intValue=(option_var_name, current_state))
|
||||
|
||||
# Restore selection
|
||||
if selection:
|
||||
cmds.select(selection, replace=True)
|
||||
|
||||
|
||||
def sg_SeamlessSwitching(
|
||||
FK_Joint_Root, FK_Joint_Mid, FK_Joint_End,
|
||||
FK_Ctrl_Root, FK_Ctrl_Mid, FK_Ctrl_End,
|
||||
IK_Joint_Root, IK_Joint_Mid, IK_Joint_End,
|
||||
IK_Ctrl_Root, IK_Ctrl_Pole,
|
||||
Switch_Ctrl, Switch_Attr
|
||||
):
|
||||
"""
|
||||
Sets up the necessary nodes, attributes, and the scriptJob for IK/FK switching.
|
||||
"""
|
||||
|
||||
# 1. Ensure all objects exist
|
||||
all_objects = [FK_Joint_Root, FK_Joint_Mid, FK_Joint_End, FK_Ctrl_Root, FK_Ctrl_Mid,
|
||||
FK_Ctrl_End, IK_Joint_Root, IK_Joint_Mid, IK_Joint_End, IK_Ctrl_Root,
|
||||
IK_Ctrl_Pole, Switch_Ctrl]
|
||||
|
||||
for obj in all_objects:
|
||||
if not cmds.objExists(obj):
|
||||
cmds.error("Object not found: '{}'. Please verify all joint and controller names.".format(obj))
|
||||
return
|
||||
|
||||
# 2. Cleanup and Create Locator
|
||||
LOCATOR_NAME = "{}_Switch_Locator".format(Switch_Ctrl)
|
||||
|
||||
matching_locators = cmds.ls(LOCATOR_NAME + "*", type='transform')
|
||||
final_locators_to_delete = []
|
||||
for loc in matching_locators:
|
||||
if loc.startswith(LOCATOR_NAME) and cmds.listRelatives(loc, shapes=True, type='locator'):
|
||||
final_locators_to_delete.append(loc)
|
||||
|
||||
if final_locators_to_delete:
|
||||
cmds.warning("Found and deleting {} existing switch locators for {}: {}".format(
|
||||
len(final_locators_to_delete), Switch_Ctrl, final_locators_to_delete))
|
||||
try:
|
||||
cmds.delete(final_locators_to_delete)
|
||||
except Exception as e:
|
||||
cmds.warning("Failed to delete old locators: {}".format(e))
|
||||
|
||||
# Recreate Locator
|
||||
locator_shape = cmds.createNode("locator", name=LOCATOR_NAME + "Shape")
|
||||
locator = cmds.listRelatives(locator_shape, parent=True)[0]
|
||||
locator = cmds.rename(locator, LOCATOR_NAME)
|
||||
|
||||
cmds.parent(locator, Switch_Ctrl)
|
||||
|
||||
# Unlock and clear transform attributes
|
||||
for attr in ['tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz', 'v']:
|
||||
full_attr = "{}.{}".format(locator, attr)
|
||||
cmds.setAttr(full_attr, lock=False)
|
||||
connections = cmds.listConnections(full_attr, plugs=True, destination=False, source=True)
|
||||
if connections:
|
||||
cmds.disconnectAttr(connections[0], full_attr)
|
||||
|
||||
# Reset and hide Locator
|
||||
cmds.setAttr("{}.t".format(locator), 0, 0, 0)
|
||||
cmds.setAttr("{}.r".format(locator), 0, 0, 0)
|
||||
cmds.setAttr("{}.s".format(locator), 1, 1, 1)
|
||||
cmds.setAttr("{}.visibility".format(locator), 0)
|
||||
|
||||
|
||||
# 3. Add IKFK_Seamless attribute to Switch Ctrl
|
||||
if not cmds.attributeQuery("IKFK_Seamless", node=Switch_Ctrl, exists=True):
|
||||
cmds.addAttr(Switch_Ctrl, longName="IKFK_Seamless", attributeType="enum",
|
||||
enumName="IK:FK:", keyable=False)
|
||||
cmds.setAttr("{}.IKFK_Seamless".format(Switch_Ctrl), channelBox=True)
|
||||
|
||||
# 4. Add config attributes to Locator and connect
|
||||
|
||||
config_map = {
|
||||
"IKFK_Seamless_Switching": LOCATOR_NAME,
|
||||
"FK_Joint_Root": FK_Joint_Root,
|
||||
"FK_Joint_Mid": FK_Joint_Mid,
|
||||
"FK_Joint_End": FK_Joint_End,
|
||||
"FK_Ctrl_Root": FK_Ctrl_Root,
|
||||
"FK_Ctrl_Mid": FK_Ctrl_Mid,
|
||||
"FK_Ctrl_End": FK_Ctrl_End,
|
||||
"IK_Joint_Root": IK_Joint_Root,
|
||||
"IK_Joint_Mid": IK_Joint_Mid,
|
||||
"IK_Joint_End": IK_Joint_End,
|
||||
"IK_Ctrl_Root": IK_Ctrl_Root,
|
||||
"IK_Ctrl_Pole": IK_Ctrl_Pole,
|
||||
"Switch_Ctrl": Switch_Ctrl,
|
||||
"Switch_Attr": Switch_Attr,
|
||||
}
|
||||
|
||||
for attr_name, value in config_map.items():
|
||||
attr_full_name = "{}.{}".format(locator, attr_name)
|
||||
|
||||
if not cmds.attributeQuery(attr_name, node=locator, exists=True):
|
||||
cmds.addAttr(locator, longName=attr_name, dataType="string", keyable=True)
|
||||
|
||||
cmds.setAttr(attr_full_name, value, type="string")
|
||||
|
||||
# 6. Set up scriptJob (Uses the full module path)
|
||||
# Note: Old scriptJobs will be automatically replaced when the same attribute is monitored
|
||||
|
||||
# The command string for scriptJob - pure Python code
|
||||
# Format: 'import module; module.sg_switching("locator_name")'
|
||||
command = 'import {0}; {0}.sg_switching("{1}")'.format(MODULE_NAME, LOCATOR_NAME)
|
||||
|
||||
try:
|
||||
cmds.scriptJob(attributeChange=["{}.IKFK_Seamless".format(Switch_Ctrl), command],
|
||||
killWithScene=True)
|
||||
|
||||
cmds.inViewMessage(message='IK/FK Seamless Switching Built for: <hl>{}</hl>'.format(Switch_Ctrl),
|
||||
position='topCenter', fade=True)
|
||||
|
||||
except Exception as e:
|
||||
cmds.error("Failed to set up scriptJob: {}. Command was: {}".format(e, command))
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
IKFK_Switch_UI()
|
||||
@@ -1,461 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
IKFK Switch Tool V1.0
|
||||
Seamless switching between IK and FK animation for Maya rigs
|
||||
Compatible with Maya 2018+
|
||||
"""
|
||||
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
|
||||
|
||||
class IKFKSwitchUI(object):
|
||||
"""IKFK Switch UI Class"""
|
||||
|
||||
WINDOW_NAME = "IKFK_Switch_UI"
|
||||
WINDOW_TITLE = "IKFK V1.0"
|
||||
WINDOW_WIDTH = 300
|
||||
WINDOW_HEIGHT = 578
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize UI"""
|
||||
self.target_fields = {}
|
||||
self.target_values = {
|
||||
'target_01': '', # FK Joint Root
|
||||
'target_02': '', # FK Joint Mid
|
||||
'target_03': '', # FK Joint End
|
||||
'target_04': '', # FK Ctrl Root
|
||||
'target_05': '', # FK Ctrl Mid
|
||||
'target_06': '', # FK Ctrl End
|
||||
'target_07': '', # IK Ctrl Root
|
||||
'target_08': '', # IK Ctrl Pole
|
||||
'target_09': '', # Switch Ctrl
|
||||
'target_10': '' # Switch Attr
|
||||
}
|
||||
|
||||
def create_ui(self):
|
||||
"""Create the main UI window"""
|
||||
# Delete existing window if it exists
|
||||
if cmds.window(self.WINDOW_NAME, exists=True):
|
||||
cmds.deleteUI(self.WINDOW_NAME)
|
||||
|
||||
# Create window
|
||||
cmds.window(
|
||||
self.WINDOW_NAME,
|
||||
title=self.WINDOW_TITLE,
|
||||
widthHeight=(self.WINDOW_WIDTH, self.WINDOW_HEIGHT),
|
||||
sizeable=True
|
||||
)
|
||||
|
||||
# Main layout
|
||||
main_layout = cmds.columnLayout(rowSpacing=1, adjustableColumn=True)
|
||||
|
||||
# Edit frame
|
||||
edit_frame = cmds.frameLayout(
|
||||
label="Edit",
|
||||
collapsable=True,
|
||||
collapse=True,
|
||||
collapseCommand=lambda: cmds.window(self.WINDOW_NAME, edit=True, height=self.WINDOW_HEIGHT)
|
||||
)
|
||||
cmds.columnLayout(rowSpacing=2, adjustableColumn=True)
|
||||
cmds.button(label="<<< ADV Build >>>", command=lambda x: self.adv_build())
|
||||
cmds.button(label="<<< Empty >>>", command=lambda x: self.empty_fields())
|
||||
cmds.setParent('..')
|
||||
cmds.setParent('..')
|
||||
|
||||
# FK Controls frame
|
||||
fk_frame = cmds.frameLayout(label="Load FK Ctrl")
|
||||
cmds.columnLayout(rowSpacing=2, adjustableColumn=True)
|
||||
|
||||
self._create_text_field_row('target_01', '< FK Joint Root')
|
||||
self._create_text_field_row('target_02', '< FK Joint Mid')
|
||||
self._create_text_field_row('target_03', '< FK Joint End')
|
||||
cmds.separator(style='in', height=5)
|
||||
self._create_text_field_row('target_04', '< FK Ctrl Root')
|
||||
self._create_text_field_row('target_05', '< FK Ctrl Mid')
|
||||
self._create_text_field_row('target_06', '< FK Ctrl End')
|
||||
|
||||
cmds.setParent('..')
|
||||
cmds.setParent('..')
|
||||
|
||||
# IK Controls frame
|
||||
ik_frame = cmds.frameLayout(label="Load IK Ctrl")
|
||||
cmds.columnLayout(rowSpacing=2, adjustableColumn=True)
|
||||
|
||||
self._create_text_field_row('target_07', '< IK Ctrl Root')
|
||||
self._create_text_field_row('target_08', '< IK Ctrl Pole')
|
||||
|
||||
cmds.setParent('..')
|
||||
cmds.setParent('..')
|
||||
|
||||
# Switch Control frame
|
||||
switch_frame = cmds.frameLayout(label="Load Switch Ctrl")
|
||||
cmds.columnLayout(rowSpacing=2, adjustableColumn=True)
|
||||
|
||||
self._create_text_field_row('target_09', '< Switch Ctrl')
|
||||
|
||||
# Special row for attribute selection
|
||||
cmds.rowLayout(numberOfColumns=2, adjustableColumn=1, columnWidth2=(45, 60))
|
||||
self.target_fields['target_10'] = cmds.textField(text='')
|
||||
cmds.button(width=120, label='< Switch Attr', command=lambda x: self.load_attr())
|
||||
cmds.setParent('..')
|
||||
|
||||
cmds.setParent('..')
|
||||
cmds.setParent('..')
|
||||
|
||||
# Execute button
|
||||
cmds.separator(style='in', height=5)
|
||||
cmds.button(height=32, label="<<< Build Seamless Switching >>>", command=lambda x: self.execute())
|
||||
|
||||
# Show window
|
||||
cmds.window(self.WINDOW_NAME, edit=True, height=self.WINDOW_HEIGHT)
|
||||
cmds.showWindow(self.WINDOW_NAME)
|
||||
|
||||
def _create_text_field_row(self, target_name, button_label):
|
||||
"""Create a text field row with button"""
|
||||
cmds.rowLayout(numberOfColumns=2, adjustableColumn=1, columnWidth2=(45, 60))
|
||||
self.target_fields[target_name] = cmds.textField(text=self.target_values[target_name])
|
||||
cmds.button(
|
||||
width=120,
|
||||
label=button_label,
|
||||
command=lambda x: self.set_text_field(target_name)
|
||||
)
|
||||
cmds.setParent('..')
|
||||
|
||||
def set_text_field(self, target_name):
|
||||
"""Set text field from selection"""
|
||||
selection = cmds.ls(selection=True)
|
||||
if selection:
|
||||
obj_name = selection[0]
|
||||
cmds.textField(self.target_fields[target_name], edit=True, text=obj_name)
|
||||
self.target_values[target_name] = obj_name
|
||||
|
||||
def load_attr(self):
|
||||
"""Load attribute from channel box selection"""
|
||||
try:
|
||||
# Get selected attributes from channel box
|
||||
attrs = cmds.channelBox('mainChannelBox', query=True, selectedMainAttributes=True)
|
||||
if not attrs:
|
||||
cmds.error("Select an attribute...")
|
||||
return
|
||||
|
||||
attr_name = attrs[0]
|
||||
cmds.textField(self.target_fields['target_10'], edit=True, text=attr_name)
|
||||
self.target_values['target_10'] = attr_name
|
||||
except Exception as e:
|
||||
cmds.warning("Failed to load attribute: {}".format(e))
|
||||
|
||||
def execute(self):
|
||||
"""Execute the seamless switching setup"""
|
||||
# Get values from text fields
|
||||
fk_joint_root = cmds.textField(self.target_fields['target_01'], query=True, text=True)
|
||||
fk_joint_mid = cmds.textField(self.target_fields['target_02'], query=True, text=True)
|
||||
fk_joint_end = cmds.textField(self.target_fields['target_03'], query=True, text=True)
|
||||
fk_ctrl_root = cmds.textField(self.target_fields['target_04'], query=True, text=True)
|
||||
fk_ctrl_mid = cmds.textField(self.target_fields['target_05'], query=True, text=True)
|
||||
fk_ctrl_end = cmds.textField(self.target_fields['target_06'], query=True, text=True)
|
||||
ik_ctrl_root = cmds.textField(self.target_fields['target_07'], query=True, text=True)
|
||||
ik_ctrl_pole = cmds.textField(self.target_fields['target_08'], query=True, text=True)
|
||||
switch_ctrl = cmds.textField(self.target_fields['target_09'], query=True, text=True)
|
||||
switch_attr = cmds.textField(self.target_fields['target_10'], query=True, text=True)
|
||||
|
||||
# Call the seamless switching function
|
||||
# Note: IK joints use FK joints for reference (same as MEL version)
|
||||
seamless_switching(
|
||||
fk_joint_root, fk_joint_mid, fk_joint_end,
|
||||
fk_ctrl_root, fk_ctrl_mid, fk_ctrl_end,
|
||||
fk_joint_root, fk_joint_mid, fk_joint_end, # IK joints = FK joints
|
||||
ik_ctrl_root, ik_ctrl_pole,
|
||||
switch_ctrl, switch_attr
|
||||
)
|
||||
|
||||
def adv_build(self):
|
||||
"""Build ADV rig presets"""
|
||||
# Right arm
|
||||
seamless_switching(
|
||||
"FKXShoulder_R", "FKXElbow_R", "FKXWrist_R",
|
||||
"FKShoulder_R", "FKElbow_R", "FKWrist_R",
|
||||
"IKXShoulder_R", "IKXElbow_R", "IKXWrist_R",
|
||||
"IKArm_R", "PoleArm_R",
|
||||
"FKIKArm_R", "FKIKBlend"
|
||||
)
|
||||
|
||||
# Left arm
|
||||
seamless_switching(
|
||||
"FKXShoulder_L", "FKXElbow_L", "FKXWrist_L",
|
||||
"FKShoulder_L", "FKElbow_L", "FKWrist_L",
|
||||
"IKXShoulder_L", "IKXElbow_L", "IKXWrist_L",
|
||||
"IKArm_L", "PoleArm_L",
|
||||
"FKIKArm_L", "FKIKBlend"
|
||||
)
|
||||
|
||||
# Right leg
|
||||
seamless_switching(
|
||||
"FKXHip_R", "FKXKnee_R", "FKXAnkle_R",
|
||||
"FKHip_R", "FKKnee_R", "FKAnkle_R",
|
||||
"IKXHip_R", "IKXKnee_R", "IKXAnkle_R",
|
||||
"IKLeg_R", "PoleLeg_R",
|
||||
"FKIKLeg_R", "FKIKBlend"
|
||||
)
|
||||
|
||||
# Left leg
|
||||
seamless_switching(
|
||||
"FKXHip_L", "FKXKnee_L", "FKXAnkle_L",
|
||||
"FKHip_L", "FKKnee_L", "FKAnkle_L",
|
||||
"IKXHip_L", "IKXKnee_L", "IKXAnkle_L",
|
||||
"IKLeg_L", "PoleLeg_L",
|
||||
"FKIKLeg_L", "FKIKBlend"
|
||||
)
|
||||
|
||||
def empty_fields(self):
|
||||
"""Clear all text fields"""
|
||||
for target_name in self.target_values.keys():
|
||||
self.target_values[target_name] = ''
|
||||
if target_name in self.target_fields:
|
||||
cmds.textField(self.target_fields[target_name], edit=True, text='')
|
||||
|
||||
|
||||
def seamless_switching(
|
||||
fk_joint_root, fk_joint_mid, fk_joint_end,
|
||||
fk_ctrl_root, fk_ctrl_mid, fk_ctrl_end,
|
||||
ik_joint_root, ik_joint_mid, ik_joint_end,
|
||||
ik_ctrl_root, ik_ctrl_pole,
|
||||
switch_ctrl, switch_attr
|
||||
):
|
||||
"""
|
||||
Setup seamless IKFK switching
|
||||
|
||||
Args:
|
||||
fk_joint_root: FK joint root
|
||||
fk_joint_mid: FK joint mid
|
||||
fk_joint_end: FK joint end
|
||||
fk_ctrl_root: FK control root
|
||||
fk_ctrl_mid: FK control mid
|
||||
fk_ctrl_end: FK control end
|
||||
ik_joint_root: IK joint root
|
||||
ik_joint_mid: IK joint mid
|
||||
ik_joint_end: IK joint end
|
||||
ik_ctrl_root: IK control root
|
||||
ik_ctrl_pole: IK pole vector control
|
||||
switch_ctrl: Switch control
|
||||
switch_attr: Switch attribute name
|
||||
"""
|
||||
# Create locator for storing data
|
||||
locator_name = "{}_Switch_Locator".format(switch_ctrl)
|
||||
|
||||
if not cmds.objExists(locator_name):
|
||||
locator_shape = cmds.createNode('locator', name=locator_name)
|
||||
locator_transform = cmds.listRelatives(locator_shape, parent=True)[0]
|
||||
cmds.parent(locator_shape, switch_ctrl, shape=True, add=True)
|
||||
cmds.delete(locator_transform)
|
||||
cmds.setAttr("{}.visibility".format(locator_name), 0)
|
||||
|
||||
# Add IKFK_Seamless attribute if it doesn't exist
|
||||
if not cmds.objExists("{}.IKFK_Seamless".format(switch_ctrl)):
|
||||
cmds.addAttr(switch_ctrl, longName="IKFK_Seamless", attributeType="enum", enumName="IK:FK:", keyable=False)
|
||||
cmds.setAttr("{}.IKFK_Seamless".format(switch_ctrl), channelBox=True)
|
||||
|
||||
# Add Location attribute to all objects
|
||||
objs = [
|
||||
fk_joint_root, fk_joint_mid, fk_joint_end,
|
||||
fk_ctrl_root, fk_ctrl_mid, fk_ctrl_end,
|
||||
ik_joint_root, ik_joint_mid, ik_joint_end,
|
||||
ik_ctrl_root, ik_ctrl_pole, switch_ctrl
|
||||
]
|
||||
|
||||
for obj in objs:
|
||||
# Check if object name is not empty and exists
|
||||
if obj and cmds.objExists(obj):
|
||||
if not cmds.objExists("{}.Location".format(obj)):
|
||||
cmds.addAttr(obj, longName="Location", dataType="string", keyable=True)
|
||||
else:
|
||||
cmds.warning("Object does not exist or is empty: {}".format(obj))
|
||||
|
||||
# Add attributes to locator and connect
|
||||
attrs_data = {
|
||||
"IKFK_Seamless_Switching": locator_name,
|
||||
"FK_Joint_Root": fk_joint_root,
|
||||
"FK_Joint_Mid": fk_joint_mid,
|
||||
"FK_Joint_End": fk_joint_end,
|
||||
"FK_Ctrl_Root": fk_ctrl_root,
|
||||
"FK_Ctrl_Mid": fk_ctrl_mid,
|
||||
"FK_Ctrl_End": fk_ctrl_end,
|
||||
"IK_Joint_Root": ik_joint_root,
|
||||
"IK_Joint_Mid": ik_joint_mid,
|
||||
"IK_Joint_End": ik_joint_end,
|
||||
"IK_Ctrl_Root": ik_ctrl_root,
|
||||
"IK_Ctrl_Pole": ik_ctrl_pole,
|
||||
"Switch_Ctrl": switch_ctrl,
|
||||
"Switch_Attr": switch_attr
|
||||
}
|
||||
|
||||
for attr_name, attr_value in attrs_data.items():
|
||||
if not cmds.objExists("{}.{}".format(locator_name, attr_name)):
|
||||
cmds.addAttr(locator_name, longName=attr_name, dataType="string", keyable=True)
|
||||
|
||||
cmds.setAttr("{}.{}".format(locator_name, attr_name), attr_value, type="string")
|
||||
|
||||
# Connect to Location attribute (except for special attributes)
|
||||
if attr_name not in ["IKFK_Seamless_Switching", "Switch_Attr"]:
|
||||
target_obj = attr_value
|
||||
if cmds.objExists("{}.Location".format(target_obj)):
|
||||
if not cmds.isConnected("{}.{}".format(locator_name, attr_name), "{}.Location".format(target_obj)):
|
||||
cmds.connectAttr("{}.{}".format(locator_name, attr_name), "{}.Location".format(target_obj), force=True)
|
||||
|
||||
# Create switching script
|
||||
create_switching_script()
|
||||
|
||||
# Create script job for this control
|
||||
create_script_job(switch_ctrl, locator_name)
|
||||
|
||||
print("IKFK Seamless Switching setup completed for {}".format(switch_ctrl))
|
||||
|
||||
|
||||
def create_switching_script():
|
||||
"""Create the global switching script"""
|
||||
script_node_name = "SG_IKFK_Switching_Script"
|
||||
|
||||
# MEL script for switching logic
|
||||
mel_script = '''
|
||||
global proc sg_switching (string $Switch)
|
||||
{
|
||||
int $state_tmp;
|
||||
if (!`optionVar -ex $Switch`){
|
||||
$state_tmp = 0;
|
||||
}
|
||||
$state_tmp = `optionVar -q $Switch`;
|
||||
string $tmp[] = `listConnections ($Switch+".Switch_Ctrl")`;
|
||||
string $Switch_Ctrl = $tmp[0];
|
||||
int $state=`getAttr ($Switch_Ctrl+".IKFK_Seamless")`;
|
||||
if($state_tmp == $state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string $sel[] = `ls -sl`;
|
||||
string $tmp01[] = `listConnections ($Switch+".FK_Joint_Root")`;
|
||||
string $tmp02[] = `listConnections ($Switch+".FK_Joint_Mid")`;
|
||||
string $tmp03[] = `listConnections ($Switch+".FK_Joint_End")`;
|
||||
string $tmp04[] = `listConnections ($Switch+".FK_Ctrl_Root")`;
|
||||
string $tmp05[] = `listConnections ($Switch+".FK_Ctrl_Mid")`;
|
||||
string $tmp06[] = `listConnections ($Switch+".FK_Ctrl_End")`;
|
||||
string $tmp07[] = `listConnections ($Switch+".IK_Joint_Root")`;
|
||||
string $tmp08[] = `listConnections ($Switch+".IK_Joint_Mid")`;
|
||||
string $tmp09[] = `listConnections ($Switch+".IK_Joint_End")`;
|
||||
string $tmp10[] = `listConnections ($Switch+".IK_Ctrl_Root")`;
|
||||
string $tmp11[] = `listConnections ($Switch+".IK_Ctrl_Pole")`;
|
||||
|
||||
string $FK_Joint_Root = $tmp01[0];
|
||||
string $FK_Joint_Mid = $tmp02[0];
|
||||
string $FK_Joint_End = $tmp03[0];
|
||||
string $FK_Ctrl_Root = $tmp04[0];
|
||||
string $FK_Ctrl_Mid = $tmp05[0];
|
||||
string $FK_Ctrl_End = $tmp06[0];
|
||||
string $IK_Joint_Root = $tmp07[0];
|
||||
string $IK_Joint_Mid = $tmp08[0];
|
||||
string $IK_Joint_End = $tmp09[0];
|
||||
string $IK_Ctrl_Root = $tmp10[0];
|
||||
string $IK_Ctrl_Pole = $tmp11[0];
|
||||
|
||||
string $Switch_Attr = `getAttr ($Switch+".Switch_Attr")`;
|
||||
|
||||
int $min = `addAttr -q -min ($Switch_Ctrl+"."+$Switch_Attr)`;
|
||||
int $max = `addAttr -q -max ($Switch_Ctrl+"."+$Switch_Attr)`;
|
||||
int $time=`currentTime -q`;
|
||||
setKeyframe $FK_Ctrl_Root;
|
||||
setKeyframe $FK_Ctrl_Mid;
|
||||
setKeyframe $FK_Ctrl_End;
|
||||
setKeyframe $IK_Ctrl_Root;
|
||||
setKeyframe $IK_Ctrl_Pole;
|
||||
if($state==0){
|
||||
string $tempGroup_A = `group -em`;
|
||||
string $tempGroup_B = `group -em`;
|
||||
delete `parentConstraint -weight 1 $IK_Joint_End $tempGroup_A`;
|
||||
delete `parentConstraint -weight 1 $IK_Joint_End $tempGroup_B`;
|
||||
delete `orientConstraint -offset 0 0 0 -weight 1 $IK_Ctrl_Root $tempGroup_B`;
|
||||
parent $tempGroup_B $tempGroup_A;
|
||||
delete `parentConstraint -weight 1 $FK_Joint_End $tempGroup_A`;
|
||||
string $con_A[] = `parentConstraint -weight 1 $tempGroup_B $IK_Ctrl_Root`;
|
||||
setKeyframe $IK_Ctrl_Root;
|
||||
delete $con_A;
|
||||
string $con_B[] = `pointConstraint -offset 0 0 0 -weight 1 $FK_Joint_Mid $IK_Ctrl_Pole`;
|
||||
setKeyframe $IK_Ctrl_Pole;
|
||||
delete $con_B;
|
||||
setAttr ($Switch_Ctrl+"."+$Switch_Attr) $min;
|
||||
setKeyframe -t ($time-1) -at $Switch_Attr $Switch_Ctrl;
|
||||
setAttr ($Switch_Ctrl+"."+$Switch_Attr) $max;
|
||||
setKeyframe -t $time -at $Switch_Attr $Switch_Ctrl;
|
||||
delete $tempGroup_A;
|
||||
}
|
||||
if($state==1){
|
||||
string $con_A[] = `parentConstraint -weight 1 $IK_Joint_Root $FK_Ctrl_Root`;
|
||||
setKeyframe $FK_Ctrl_Root;
|
||||
delete $con_A;
|
||||
string $con_B[] = `parentConstraint -weight 1 $IK_Joint_Mid $FK_Ctrl_Mid`;
|
||||
setKeyframe $FK_Ctrl_Mid;
|
||||
delete $con_B;
|
||||
string $con_C[] = `parentConstraint -weight 1 $IK_Joint_End $FK_Ctrl_End`;
|
||||
setKeyframe $FK_Ctrl_End;
|
||||
delete $con_C;
|
||||
setAttr ($Switch_Ctrl+"."+$Switch_Attr) $max;
|
||||
setKeyframe -t ($time-1) -at $Switch_Attr $Switch_Ctrl;
|
||||
setAttr ($Switch_Ctrl+"."+$Switch_Attr) $min;
|
||||
setKeyframe -t $time -at $Switch_Attr $Switch_Ctrl;
|
||||
}
|
||||
optionVar -iv $Switch $state;
|
||||
select -r $sel;
|
||||
}
|
||||
'''
|
||||
|
||||
if not cmds.objExists(script_node_name):
|
||||
cmds.scriptNode(beforeScript=mel_script, name=script_node_name)
|
||||
else:
|
||||
cmds.scriptNode(script_node_name, edit=True, beforeScript=mel_script)
|
||||
|
||||
cmds.setAttr("{}.scriptType".format(script_node_name), 1)
|
||||
cmds.scriptNode(script_node_name, executeBefore=True)
|
||||
|
||||
|
||||
def create_script_job(switch_ctrl, locator_name):
|
||||
"""Create script job for automatic switching"""
|
||||
script_node_name = "{}_Switching_Script".format(switch_ctrl)
|
||||
|
||||
# MEL script for script job creation
|
||||
mel_script = '''
|
||||
string $Locator_all[]=`ls -typ "locator"`;
|
||||
for($Locator in $Locator_all){{
|
||||
if(`objExists ($Locator+".IKFK_Seamless_Switching")`){{
|
||||
string $tmp=`getAttr ($Locator+".IKFK_Seamless_Switching")`;
|
||||
if($tmp=="{0}"){{
|
||||
string $Switch[]=`listConnections ($Locator+".Switch_Ctrl")`;
|
||||
scriptJob -ac ($Switch[0]+".IKFK_Seamless") ("sg_switching "+$Locator) -kws;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
'''.format(locator_name)
|
||||
|
||||
if not cmds.objExists(script_node_name):
|
||||
cmds.scriptNode(beforeScript=mel_script, name=script_node_name)
|
||||
else:
|
||||
cmds.scriptNode(script_node_name, edit=True, beforeScript=mel_script)
|
||||
|
||||
cmds.setAttr("{}.scriptType".format(script_node_name), 1)
|
||||
cmds.scriptNode(script_node_name, executeBefore=True)
|
||||
|
||||
|
||||
def show():
|
||||
"""Main entry point to show the UI"""
|
||||
ui = IKFKSwitchUI()
|
||||
ui.create_ui()
|
||||
|
||||
|
||||
# For backwards compatibility
|
||||
def start():
|
||||
"""Alternative entry point"""
|
||||
show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
show()
|
||||
@@ -69,7 +69,7 @@ global proc shelf_Nexus_Animation () {
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "import animation_tools.ikfx_switch\nanimation_tools.ikfx_switch.show()"
|
||||
-command "from animation_tools import ikfk_switch\nikfk_switch.IKFK_Switch_UI()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
|
||||
603
2025/scripts/animation_tools/ikfk_switch.py
Normal file
603
2025/scripts/animation_tools/ikfk_switch.py
Normal file
@@ -0,0 +1,603 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
import math
|
||||
import sys
|
||||
import inspect
|
||||
import importlib
|
||||
|
||||
# Crucial for fixing namespace issues:
|
||||
# Dynamically gets the module name (e.g., 'animation_tools.ikfk_switch')
|
||||
|
||||
MODULE_NAME = __name__
|
||||
|
||||
# ==============================================================================
|
||||
# UI Creation
|
||||
# ==============================================================================
|
||||
|
||||
def IKFK_Switch_UI():
|
||||
"""
|
||||
Creates the main IK/FK switch Maya window interface.
|
||||
"""
|
||||
WINDOW_NAME = "IKFK_Switch_UI"
|
||||
WINDOW_TITLE = "IK/FK Switch V2.9 (Final Button Fix)"
|
||||
WINDOW_WIDTH = 300
|
||||
|
||||
# Check if window exists and delete it
|
||||
if cmds.window(WINDOW_NAME, exists=True):
|
||||
cmds.deleteUI(WINDOW_NAME, window=True)
|
||||
|
||||
# Create window
|
||||
cmds.window(WINDOW_NAME, width=WINDOW_WIDTH, title=WINDOW_TITLE)
|
||||
|
||||
# Main Layout
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=1)
|
||||
|
||||
# --- Helper function for button commands ---
|
||||
# CORE FIX: Removed the 'python("...")' wrapper.
|
||||
# The function now returns a pure Python command string for button execution.
|
||||
def py_cmd(func_name, args=""):
|
||||
# Format: 'import module; module.function("args")' (Pure Python string)
|
||||
if args:
|
||||
# Use double quotes to avoid escaping issues
|
||||
return 'import {0}; {0}.{1}("{2}")'.format(MODULE_NAME, func_name, args)
|
||||
else:
|
||||
return 'import {0}; {0}.{1}()'.format(MODULE_NAME, func_name)
|
||||
|
||||
# --- Edit Section ---
|
||||
cmds.frameLayout(label="Edit", collapse=True, collapsable=True,
|
||||
collapseCommand="cmds.window('{}', edit=True, height=578)".format(WINDOW_NAME),
|
||||
expandCommand="cmds.window('{}', edit=True, height=578)".format(WINDOW_NAME))
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=2)
|
||||
cmds.button(label="<<< ADV Build >>>", command=py_cmd("sg_ADV_Build"))
|
||||
cmds.button(label="<<< Empty >>>", command=py_cmd("sg_Empty"))
|
||||
cmds.setParent("..")
|
||||
cmds.setParent("..")
|
||||
|
||||
# --- Load FK Ctrl Section ---
|
||||
cmds.frameLayout(label="Load FK Ctrl")
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=2)
|
||||
|
||||
# FK Joint Root
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_01", text="", placeholderText="FK Joint Root")
|
||||
cmds.button(label="< FK Joint Root", command=py_cmd("sg_setTextField", "target_01"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# FK Joint Mid
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_02", text="", placeholderText="FK Joint Mid")
|
||||
cmds.button(label="< FK Joint Mid", command=py_cmd("sg_setTextField", "target_02"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# FK Joint End
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_03", text="", placeholderText="FK Joint End")
|
||||
cmds.button(label="< FK Joint End", command=py_cmd("sg_setTextField", "target_03"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.separator(style="in", height=5)
|
||||
|
||||
# FK Ctrl Root
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_04", text="", placeholderText="FK Ctrl Root")
|
||||
cmds.button(label="< FK Ctrl Root", command=py_cmd("sg_setTextField", "target_04"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# FK Ctrl Mid
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_05", text="", placeholderText="FK Ctrl Mid")
|
||||
cmds.button(label="< FK Ctrl Mid", command=py_cmd("sg_setTextField", "target_05"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# FK Ctrl End
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_06", text="", placeholderText="FK Ctrl End")
|
||||
cmds.button(label="< FK Ctrl End", command=py_cmd("sg_setTextField", "target_06"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.setParent("..")
|
||||
cmds.setParent("..")
|
||||
|
||||
# --- Load IK Ctrl Section ---
|
||||
cmds.frameLayout(label="Load IK Ctrl")
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=2)
|
||||
|
||||
# IK Joint Root
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_07", text="", placeholderText="IK Joint Root")
|
||||
cmds.button(label="< IK Joint Root", command=py_cmd("sg_setTextField", "target_07"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# IK Joint Mid
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_08", text="", placeholderText="IK Joint Mid")
|
||||
cmds.button(label="< IK Joint Mid", command=py_cmd("sg_setTextField", "target_08"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# IK Joint End
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_09", text="", placeholderText="IK Joint End")
|
||||
cmds.button(label="< IK Joint End", command=py_cmd("sg_setTextField", "target_09"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.separator(style="in", height=5)
|
||||
|
||||
# IK Ctrl Root
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_10", text="", placeholderText="IK Ctrl Root")
|
||||
cmds.button(label="< IK Ctrl Root", command=py_cmd("sg_setTextField", "target_10"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# IK Ctrl Pole
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_11", text="", placeholderText="IK Ctrl Pole")
|
||||
cmds.button(label="< IK Ctrl Pole", command=py_cmd("sg_setTextField", "target_11"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.setParent("..")
|
||||
cmds.setParent("..")
|
||||
|
||||
# --- Load Switch Ctrl Section ---
|
||||
cmds.frameLayout(label="Load Switch Ctrl")
|
||||
cmds.columnLayout(adjustableColumn=True, rowSpacing=2)
|
||||
|
||||
# Switch Ctrl
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_12", text="", placeholderText="Switch Ctrl")
|
||||
cmds.button(label="< Switch Ctrl", command=py_cmd("sg_setTextField", "target_12"))
|
||||
cmds.setParent("..")
|
||||
|
||||
# Switch Attr
|
||||
cmds.rowLayout(numberOfColumns=2, columnWidth2=(45, 120), adjustableColumn=1)
|
||||
cmds.textField("target_13", text="", placeholderText="Switch Attr Name")
|
||||
cmds.button(label="< Switch Attr", command=py_cmd("sg_setLoadAttr"))
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.setParent("..")
|
||||
cmds.setParent("..")
|
||||
|
||||
cmds.separator(style="in", height=5)
|
||||
|
||||
# Build Button
|
||||
cmds.button(height=32, label="<<< Build Seamless Switching >>>", command=py_cmd("sg_Execute"))
|
||||
|
||||
cmds.setParent("..")
|
||||
|
||||
# Adjust window height and show
|
||||
cmds.window(WINDOW_NAME, edit=True, height=578)
|
||||
cmds.showWindow(WINDOW_NAME)
|
||||
|
||||
|
||||
def sg_setTextField(target):
|
||||
"""
|
||||
Sets the name of the currently selected object into the specified text field.
|
||||
"""
|
||||
selection = cmds.ls(selection=True, long=False)
|
||||
|
||||
if selection:
|
||||
selected_object = selection[0]
|
||||
cmds.textField(target, edit=True, text=selected_object)
|
||||
else:
|
||||
cmds.warning("Please select an object.")
|
||||
|
||||
|
||||
def sg_setLoadAttr():
|
||||
"""
|
||||
Sets the name of the selected attribute in the Channel Box into the target_13 text field.
|
||||
"""
|
||||
# Query selected attributes in the Channel Box
|
||||
attrs = cmds.channelBox("mainChannelBox", query=True, selectedMainAttributes=True)
|
||||
|
||||
if not attrs:
|
||||
cmds.error("Select an attribute in the Channel Box...")
|
||||
return
|
||||
|
||||
# Take only the first selected attribute
|
||||
attribute_name = attrs[0]
|
||||
cmds.textField("target_13", edit=True, text=attribute_name)
|
||||
|
||||
|
||||
def sg_Execute():
|
||||
"""
|
||||
Reads all values from UI controls and calls the sg_SeamlessSwitching function.
|
||||
"""
|
||||
# Get values from UI controls
|
||||
FK_Joint_Root = cmds.textField("target_01", query=True, text=True)
|
||||
FK_Joint_Mid = cmds.textField("target_02", query=True, text=True)
|
||||
FK_Joint_End = cmds.textField("target_03", query=True, text=True)
|
||||
FK_Ctrl_Root = cmds.textField("target_04", query=True, text=True)
|
||||
FK_Ctrl_Mid = cmds.textField("target_05", query=True, text=True)
|
||||
FK_Ctrl_End = cmds.textField("target_06", query=True, text=True)
|
||||
IK_Joint_Root = cmds.textField("target_07", query=True, text=True)
|
||||
IK_Joint_Mid = cmds.textField("target_08", query=True, text=True)
|
||||
IK_Joint_End = cmds.textField("target_09", query=True, text=True)
|
||||
IK_Ctrl_Root = cmds.textField("target_10", query=True, text=True)
|
||||
IK_Ctrl_Pole = cmds.textField("target_11", query=True, text=True)
|
||||
Switch_Ctrl = cmds.textField("target_12", query=True, text=True)
|
||||
Switch_Attr = cmds.textField("target_13", query=True, text=True)
|
||||
|
||||
# Check for empty values
|
||||
all_targets = [FK_Joint_Root, FK_Joint_Mid, FK_Joint_End, FK_Ctrl_Root, FK_Ctrl_Mid,
|
||||
FK_Ctrl_End, IK_Joint_Root, IK_Joint_Mid, IK_Joint_End, IK_Ctrl_Root,
|
||||
IK_Ctrl_Pole, Switch_Ctrl, Switch_Attr]
|
||||
|
||||
for target in all_targets:
|
||||
if not target:
|
||||
cmds.error("All fields must be filled before building the switch.")
|
||||
return
|
||||
|
||||
# Call core build function
|
||||
sg_SeamlessSwitching(
|
||||
FK_Joint_Root,
|
||||
FK_Joint_Mid,
|
||||
FK_Joint_End,
|
||||
FK_Ctrl_Root,
|
||||
FK_Ctrl_Mid,
|
||||
FK_Ctrl_End,
|
||||
IK_Joint_Root,
|
||||
IK_Joint_Mid,
|
||||
IK_Joint_End,
|
||||
IK_Ctrl_Root,
|
||||
IK_Ctrl_Pole,
|
||||
Switch_Ctrl,
|
||||
Switch_Attr
|
||||
)
|
||||
|
||||
|
||||
def sg_ADV_Build():
|
||||
"""
|
||||
Builds IK/FK switching for sample limbs using preset naming conventions.
|
||||
NOTE: Modify names here to match your Rig naming convention.
|
||||
"""
|
||||
|
||||
# Right Arm
|
||||
sg_SeamlessSwitching(
|
||||
"FKXShoulder_R", "FKXElbow_R", "FKXWrist_R",
|
||||
"FKShoulder_R", "FKElbow_R", "FKWrist_R",
|
||||
"IKXShoulder_R", "IKXElbow_R", "IKXWrist_R",
|
||||
"IKArm_R", "PoleArm_R", "FKIKArm_R", "fkik"
|
||||
)
|
||||
|
||||
# Left Arm
|
||||
sg_SeamlessSwitching(
|
||||
"FKXShoulder_L", "FKXElbow_L", "FKXWrist_L",
|
||||
"FKShoulder_L", "FKElbow_L", "FKWrist_L",
|
||||
"IKXShoulder_L", "IKXElbow_L", "IKXWrist_L",
|
||||
"IKArm_L", "PoleArm_L", "FKIKArm_L", "fkik"
|
||||
)
|
||||
|
||||
# Right Leg
|
||||
sg_SeamlessSwitching(
|
||||
"FKXHip_R", "FKXKnee_R", "FKXAnkle_R",
|
||||
"FKHip_R", "FKKnee_R", "FKAnkle_R",
|
||||
"IKXHip_R", "IKXKnee_R", "IKXAnkle_R",
|
||||
"IKLeg_R", "PoleLeg_R", "FKIKLeg_R", "fkik"
|
||||
)
|
||||
|
||||
# Left Leg
|
||||
sg_SeamlessSwitching(
|
||||
"FKXHip_L", "FKXKnee_L", "FKXAnkle_L",
|
||||
"FKHip_L", "FKKnee_L", "FKAnkle_L",
|
||||
"IKXHip_L", "IKXKnee_L", "IKXAnkle_L",
|
||||
"IKLeg_L", "PoleLeg_L", "FKIKLeg_L", "fkik"
|
||||
)
|
||||
|
||||
def sg_Empty():
|
||||
"""
|
||||
Clears all text fields in the UI.
|
||||
"""
|
||||
for i in range(1, 14):
|
||||
target_name = "target_{:02d}".format(i)
|
||||
if cmds.textField(target_name, exists=True):
|
||||
cmds.textField(target_name, edit=True, text="")
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Core IK/FK Switching Logic
|
||||
# ==============================================================================
|
||||
|
||||
def sg_switching(locator_name):
|
||||
"""
|
||||
Executes the core IK/FK switching logic (seamless match).
|
||||
This function is called at runtime by the scriptJob.
|
||||
|
||||
Args:
|
||||
locator_name (str): The name of the Locator storing connection info.
|
||||
"""
|
||||
|
||||
# Get the Switch Control and attribute name (stored as string attributes)
|
||||
try:
|
||||
switch_ctrl = cmds.getAttr("{}.Switch_Ctrl".format(locator_name))
|
||||
switch_attr_name = cmds.getAttr("{}.Switch_Attr".format(locator_name))
|
||||
except:
|
||||
cmds.error("Locator {} does not have required attributes.".format(locator_name))
|
||||
return
|
||||
|
||||
# State check: Use optionVar to store the last state, preventing redundant execution
|
||||
option_var_name = locator_name
|
||||
current_state = cmds.getAttr("{}.IKFK_Seamless".format(switch_ctrl))
|
||||
|
||||
try:
|
||||
last_state = cmds.optionVar(query=option_var_name)
|
||||
except RuntimeError:
|
||||
last_state = -1
|
||||
|
||||
if last_state == current_state:
|
||||
# State has not changed, exit
|
||||
return
|
||||
|
||||
# Store current selection
|
||||
selection = cmds.ls(selection=True, long=False)
|
||||
|
||||
# Get all object names from the Locator (stored as string attributes)
|
||||
try:
|
||||
FK_Joint_Root = cmds.getAttr("{}.FK_Joint_Root".format(locator_name))
|
||||
FK_Joint_Mid = cmds.getAttr("{}.FK_Joint_Mid".format(locator_name))
|
||||
FK_Joint_End = cmds.getAttr("{}.FK_Joint_End".format(locator_name))
|
||||
FK_Ctrl_Root = cmds.getAttr("{}.FK_Ctrl_Root".format(locator_name))
|
||||
FK_Ctrl_Mid = cmds.getAttr("{}.FK_Ctrl_Mid".format(locator_name))
|
||||
FK_Ctrl_End = cmds.getAttr("{}.FK_Ctrl_End".format(locator_name))
|
||||
IK_Joint_Root = cmds.getAttr("{}.IK_Joint_Root".format(locator_name))
|
||||
IK_Joint_Mid = cmds.getAttr("{}.IK_Joint_Mid".format(locator_name))
|
||||
IK_Joint_End = cmds.getAttr("{}.IK_Joint_End".format(locator_name))
|
||||
IK_Ctrl_Root = cmds.getAttr("{}.IK_Ctrl_Root".format(locator_name))
|
||||
IK_Ctrl_Pole = cmds.getAttr("{}.IK_Ctrl_Pole".format(locator_name))
|
||||
except Exception as e:
|
||||
cmds.error("Failed to retrieve object names from Locator {}: {}".format(locator_name, e))
|
||||
return
|
||||
|
||||
# Get Min/Max values for the blend attribute
|
||||
try:
|
||||
attr_min = cmds.attributeQuery(switch_attr_name, node=switch_ctrl, minimum=True)
|
||||
attr_max = cmds.attributeQuery(switch_attr_name, node=switch_ctrl, maximum=True)
|
||||
min_val = attr_min[0] if attr_min else 0.0
|
||||
max_val = attr_max[0] if attr_max else 1.0
|
||||
except Exception:
|
||||
cmds.warning("Could not query min/max values for blend attribute. Defaulting to 0 and 1.")
|
||||
min_val = 0.0
|
||||
max_val = 1.0
|
||||
|
||||
current_time = cmds.currentTime(query=True)
|
||||
|
||||
# Set keyframes on all controls before switching (MEL lines 504-508)
|
||||
cmds.setKeyframe(FK_Ctrl_Root)
|
||||
cmds.setKeyframe(FK_Ctrl_Mid)
|
||||
cmds.setKeyframe(FK_Ctrl_End)
|
||||
cmds.setKeyframe(IK_Ctrl_Root)
|
||||
cmds.setKeyframe(IK_Ctrl_Pole)
|
||||
|
||||
# **MATCHING LOGIC**
|
||||
if current_state == 0: # Switch to IK (IKFK_Seamless: IK)
|
||||
|
||||
# 1. Match IK Ctrl Root (End Controller) - Using temp groups to preserve rotation offset
|
||||
# This matches the MEL version's approach (lines 510-519)
|
||||
tempGroup_A = cmds.group(empty=True)
|
||||
tempGroup_B = cmds.group(empty=True)
|
||||
|
||||
# Step 1: Match both temp groups to IK_Joint_End
|
||||
cmds.delete(cmds.parentConstraint(IK_Joint_End, tempGroup_A, weight=1))
|
||||
cmds.delete(cmds.parentConstraint(IK_Joint_End, tempGroup_B, weight=1))
|
||||
|
||||
# Step 2: Apply IK_Ctrl_Root's current orientation to tempGroup_B (preserve rotation offset)
|
||||
cmds.delete(cmds.orientConstraint(IK_Ctrl_Root, tempGroup_B, offset=(0, 0, 0), weight=1))
|
||||
|
||||
# Step 3: Parent tempGroup_B under tempGroup_A to create hierarchy
|
||||
cmds.parent(tempGroup_B, tempGroup_A)
|
||||
|
||||
# Step 4: Match tempGroup_A to FK_Joint_End (this moves the hierarchy)
|
||||
cmds.delete(cmds.parentConstraint(FK_Joint_End, tempGroup_A, weight=1))
|
||||
|
||||
# Step 5: Apply tempGroup_B's final transform to IK_Ctrl_Root
|
||||
con_A = cmds.parentConstraint(tempGroup_B, IK_Ctrl_Root, weight=1)
|
||||
cmds.setKeyframe(IK_Ctrl_Root)
|
||||
cmds.delete(con_A)
|
||||
|
||||
# 2. Calculate Pole Vector position based on FK chain bend direction
|
||||
# Get world space positions of FK joints
|
||||
pos_root = cmds.xform(FK_Joint_Root, query=True, worldSpace=True, translation=True)
|
||||
pos_mid = cmds.xform(FK_Joint_Mid, query=True, worldSpace=True, translation=True)
|
||||
pos_end = cmds.xform(FK_Joint_End, query=True, worldSpace=True, translation=True)
|
||||
|
||||
# Calculate the bend direction using vector projection
|
||||
# Vector from root to end (main chain direction)
|
||||
vec_RE = [pos_end[i] - pos_root[i] for i in range(3)]
|
||||
# Vector from root to mid
|
||||
vec_RM = [pos_mid[i] - pos_root[i] for i in range(3)]
|
||||
|
||||
# Length of main chain vector
|
||||
len_RE = math.sqrt(sum(vec_RE[i]**2 for i in range(3)))
|
||||
|
||||
if len_RE > 0.0001: # Avoid division by zero
|
||||
# Normalize the main chain vector
|
||||
norm_RE = [vec_RE[i] / len_RE for i in range(3)]
|
||||
|
||||
# Project vec_RM onto vec_RE to find the projection point
|
||||
proj_scalar = sum(vec_RM[i] * norm_RE[i] for i in range(3))
|
||||
proj_vec = [proj_scalar * norm_RE[i] for i in range(3)]
|
||||
|
||||
# Perpendicular vector (from projection point to mid joint)
|
||||
vec_perp = [vec_RM[i] - proj_vec[i] for i in range(3)]
|
||||
len_perp = math.sqrt(sum(vec_perp[i]**2 for i in range(3)))
|
||||
|
||||
if len_perp > 0.0001: # Chain has a bend
|
||||
# Normalize perpendicular vector
|
||||
norm_perp = [vec_perp[i] / len_perp for i in range(3)]
|
||||
|
||||
# Extension distance: use chain length as reference
|
||||
# Multiply by a factor to place pole vector at a reasonable distance
|
||||
extend_dist = len_RE * 0.5
|
||||
|
||||
# Pole Vector position = mid joint + perpendicular direction * extension distance
|
||||
pole_pos = [pos_mid[i] + norm_perp[i] * extend_dist for i in range(3)]
|
||||
|
||||
else: # Chain is straight, use a default offset
|
||||
# If straight, offset along Z-axis (or another appropriate axis)
|
||||
pole_pos = [pos_mid[0], pos_mid[1], pos_mid[2] + len_RE * 0.5]
|
||||
else:
|
||||
# Fallback: keep current position if chain length is zero
|
||||
pole_pos = cmds.xform(IK_Ctrl_Pole, query=True, worldSpace=True, translation=True)
|
||||
|
||||
# Apply calculated position to Pole Vector
|
||||
cmds.xform(IK_Ctrl_Pole, worldSpace=True, translation=pole_pos)
|
||||
cmds.setKeyframe(IK_Ctrl_Pole)
|
||||
|
||||
# Clean up temp groups
|
||||
cmds.delete(tempGroup_A)
|
||||
|
||||
# 3. Switch attribute: from FK (min_val) to IK (max_val)
|
||||
cmds.setAttr("{}.{}".format(switch_ctrl, switch_attr_name), min_val)
|
||||
cmds.setKeyframe(switch_ctrl, attribute=switch_attr_name, time=current_time - 1)
|
||||
cmds.setAttr("{}.{}".format(switch_ctrl, switch_attr_name), max_val)
|
||||
cmds.setKeyframe(switch_ctrl, attribute=switch_attr_name, time=current_time)
|
||||
|
||||
|
||||
elif current_state == 1: # Switch to FK (IKFK_Seamless: FK)
|
||||
|
||||
# Match FK controls to IK joints (MEL lines 530-538)
|
||||
# Use weight=1 instead of maintainOffset to match MEL behavior
|
||||
|
||||
# 1. Match FK Ctrl Root to IK Joint Root
|
||||
con_A = cmds.parentConstraint(IK_Joint_Root, FK_Ctrl_Root, weight=1)
|
||||
cmds.setKeyframe(FK_Ctrl_Root)
|
||||
cmds.delete(con_A)
|
||||
|
||||
# 2. Match FK Ctrl Mid to IK Joint Mid
|
||||
con_B = cmds.parentConstraint(IK_Joint_Mid, FK_Ctrl_Mid, weight=1)
|
||||
cmds.setKeyframe(FK_Ctrl_Mid)
|
||||
cmds.delete(con_B)
|
||||
|
||||
# 3. Match FK Ctrl End to IK Joint End
|
||||
con_C = cmds.parentConstraint(IK_Joint_End, FK_Ctrl_End, weight=1)
|
||||
cmds.setKeyframe(FK_Ctrl_End)
|
||||
cmds.delete(con_C)
|
||||
|
||||
# 4. Switch attribute: from IK (max_val) to FK (min_val)
|
||||
cmds.setAttr("{}.{}".format(switch_ctrl, switch_attr_name), max_val)
|
||||
cmds.setKeyframe(switch_ctrl, attribute=switch_attr_name, time=current_time - 1)
|
||||
cmds.setAttr("{}.{}".format(switch_ctrl, switch_attr_name), min_val)
|
||||
cmds.setKeyframe(switch_ctrl, attribute=switch_attr_name, time=current_time)
|
||||
|
||||
# Update optionVar
|
||||
cmds.optionVar(intValue=(option_var_name, current_state))
|
||||
|
||||
# Restore selection
|
||||
if selection:
|
||||
cmds.select(selection, replace=True)
|
||||
|
||||
|
||||
def sg_SeamlessSwitching(
|
||||
FK_Joint_Root, FK_Joint_Mid, FK_Joint_End,
|
||||
FK_Ctrl_Root, FK_Ctrl_Mid, FK_Ctrl_End,
|
||||
IK_Joint_Root, IK_Joint_Mid, IK_Joint_End,
|
||||
IK_Ctrl_Root, IK_Ctrl_Pole,
|
||||
Switch_Ctrl, Switch_Attr
|
||||
):
|
||||
"""
|
||||
Sets up the necessary nodes, attributes, and the scriptJob for IK/FK switching.
|
||||
"""
|
||||
|
||||
# 1. Ensure all objects exist
|
||||
all_objects = [FK_Joint_Root, FK_Joint_Mid, FK_Joint_End, FK_Ctrl_Root, FK_Ctrl_Mid,
|
||||
FK_Ctrl_End, IK_Joint_Root, IK_Joint_Mid, IK_Joint_End, IK_Ctrl_Root,
|
||||
IK_Ctrl_Pole, Switch_Ctrl]
|
||||
|
||||
for obj in all_objects:
|
||||
if not cmds.objExists(obj):
|
||||
cmds.error("Object not found: '{}'. Please verify all joint and controller names.".format(obj))
|
||||
return
|
||||
|
||||
# 2. Cleanup and Create Locator
|
||||
LOCATOR_NAME = "{}_Switch_Locator".format(Switch_Ctrl)
|
||||
|
||||
matching_locators = cmds.ls(LOCATOR_NAME + "*", type='transform')
|
||||
final_locators_to_delete = []
|
||||
for loc in matching_locators:
|
||||
if loc.startswith(LOCATOR_NAME) and cmds.listRelatives(loc, shapes=True, type='locator'):
|
||||
final_locators_to_delete.append(loc)
|
||||
|
||||
if final_locators_to_delete:
|
||||
cmds.warning("Found and deleting {} existing switch locators for {}: {}".format(
|
||||
len(final_locators_to_delete), Switch_Ctrl, final_locators_to_delete))
|
||||
try:
|
||||
cmds.delete(final_locators_to_delete)
|
||||
except Exception as e:
|
||||
cmds.warning("Failed to delete old locators: {}".format(e))
|
||||
|
||||
# Recreate Locator
|
||||
locator_shape = cmds.createNode("locator", name=LOCATOR_NAME + "Shape")
|
||||
locator = cmds.listRelatives(locator_shape, parent=True)[0]
|
||||
locator = cmds.rename(locator, LOCATOR_NAME)
|
||||
|
||||
cmds.parent(locator, Switch_Ctrl)
|
||||
|
||||
# Unlock and clear transform attributes
|
||||
for attr in ['tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz', 'v']:
|
||||
full_attr = "{}.{}".format(locator, attr)
|
||||
cmds.setAttr(full_attr, lock=False)
|
||||
connections = cmds.listConnections(full_attr, plugs=True, destination=False, source=True)
|
||||
if connections:
|
||||
cmds.disconnectAttr(connections[0], full_attr)
|
||||
|
||||
# Reset and hide Locator
|
||||
cmds.setAttr("{}.t".format(locator), 0, 0, 0)
|
||||
cmds.setAttr("{}.r".format(locator), 0, 0, 0)
|
||||
cmds.setAttr("{}.s".format(locator), 1, 1, 1)
|
||||
cmds.setAttr("{}.visibility".format(locator), 0)
|
||||
|
||||
|
||||
# 3. Add IKFK_Seamless attribute to Switch Ctrl
|
||||
if not cmds.attributeQuery("IKFK_Seamless", node=Switch_Ctrl, exists=True):
|
||||
cmds.addAttr(Switch_Ctrl, longName="IKFK_Seamless", attributeType="enum",
|
||||
enumName="IK:FK:", keyable=False)
|
||||
cmds.setAttr("{}.IKFK_Seamless".format(Switch_Ctrl), channelBox=True)
|
||||
|
||||
# 4. Add config attributes to Locator and connect
|
||||
|
||||
config_map = {
|
||||
"IKFK_Seamless_Switching": LOCATOR_NAME,
|
||||
"FK_Joint_Root": FK_Joint_Root,
|
||||
"FK_Joint_Mid": FK_Joint_Mid,
|
||||
"FK_Joint_End": FK_Joint_End,
|
||||
"FK_Ctrl_Root": FK_Ctrl_Root,
|
||||
"FK_Ctrl_Mid": FK_Ctrl_Mid,
|
||||
"FK_Ctrl_End": FK_Ctrl_End,
|
||||
"IK_Joint_Root": IK_Joint_Root,
|
||||
"IK_Joint_Mid": IK_Joint_Mid,
|
||||
"IK_Joint_End": IK_Joint_End,
|
||||
"IK_Ctrl_Root": IK_Ctrl_Root,
|
||||
"IK_Ctrl_Pole": IK_Ctrl_Pole,
|
||||
"Switch_Ctrl": Switch_Ctrl,
|
||||
"Switch_Attr": Switch_Attr,
|
||||
}
|
||||
|
||||
for attr_name, value in config_map.items():
|
||||
attr_full_name = "{}.{}".format(locator, attr_name)
|
||||
|
||||
if not cmds.attributeQuery(attr_name, node=locator, exists=True):
|
||||
cmds.addAttr(locator, longName=attr_name, dataType="string", keyable=True)
|
||||
|
||||
cmds.setAttr(attr_full_name, value, type="string")
|
||||
|
||||
# 6. Set up scriptJob (Uses the full module path)
|
||||
# Note: Old scriptJobs will be automatically replaced when the same attribute is monitored
|
||||
|
||||
# The command string for scriptJob - pure Python code
|
||||
# Format: 'import module; module.sg_switching("locator_name")'
|
||||
command = 'import {0}; {0}.sg_switching("{1}")'.format(MODULE_NAME, LOCATOR_NAME)
|
||||
|
||||
try:
|
||||
cmds.scriptJob(attributeChange=["{}.IKFK_Seamless".format(Switch_Ctrl), command],
|
||||
killWithScene=True)
|
||||
|
||||
cmds.inViewMessage(message='IK/FK Seamless Switching Built for: <hl>{}</hl>'.format(Switch_Ctrl),
|
||||
position='topCenter', fade=True)
|
||||
|
||||
except Exception as e:
|
||||
cmds.error("Failed to set up scriptJob: {}. Command was: {}".format(e, command))
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
IKFK_Switch_UI()
|
||||
@@ -1,461 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
IKFK Switch Tool V1.0
|
||||
Seamless switching between IK and FK animation for Maya rigs
|
||||
Compatible with Maya 2018+
|
||||
"""
|
||||
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
|
||||
|
||||
class IKFKSwitchUI(object):
|
||||
"""IKFK Switch UI Class"""
|
||||
|
||||
WINDOW_NAME = "IKFK_Switch_UI"
|
||||
WINDOW_TITLE = "IKFK V1.0"
|
||||
WINDOW_WIDTH = 300
|
||||
WINDOW_HEIGHT = 578
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize UI"""
|
||||
self.target_fields = {}
|
||||
self.target_values = {
|
||||
'target_01': '', # FK Joint Root
|
||||
'target_02': '', # FK Joint Mid
|
||||
'target_03': '', # FK Joint End
|
||||
'target_04': '', # FK Ctrl Root
|
||||
'target_05': '', # FK Ctrl Mid
|
||||
'target_06': '', # FK Ctrl End
|
||||
'target_07': '', # IK Ctrl Root
|
||||
'target_08': '', # IK Ctrl Pole
|
||||
'target_09': '', # Switch Ctrl
|
||||
'target_10': '' # Switch Attr
|
||||
}
|
||||
|
||||
def create_ui(self):
|
||||
"""Create the main UI window"""
|
||||
# Delete existing window if it exists
|
||||
if cmds.window(self.WINDOW_NAME, exists=True):
|
||||
cmds.deleteUI(self.WINDOW_NAME)
|
||||
|
||||
# Create window
|
||||
cmds.window(
|
||||
self.WINDOW_NAME,
|
||||
title=self.WINDOW_TITLE,
|
||||
widthHeight=(self.WINDOW_WIDTH, self.WINDOW_HEIGHT),
|
||||
sizeable=True
|
||||
)
|
||||
|
||||
# Main layout
|
||||
main_layout = cmds.columnLayout(rowSpacing=1, adjustableColumn=True)
|
||||
|
||||
# Edit frame
|
||||
edit_frame = cmds.frameLayout(
|
||||
label="Edit",
|
||||
collapsable=True,
|
||||
collapse=True,
|
||||
collapseCommand=lambda: cmds.window(self.WINDOW_NAME, edit=True, height=self.WINDOW_HEIGHT)
|
||||
)
|
||||
cmds.columnLayout(rowSpacing=2, adjustableColumn=True)
|
||||
cmds.button(label="<<< ADV Build >>>", command=lambda x: self.adv_build())
|
||||
cmds.button(label="<<< Empty >>>", command=lambda x: self.empty_fields())
|
||||
cmds.setParent('..')
|
||||
cmds.setParent('..')
|
||||
|
||||
# FK Controls frame
|
||||
fk_frame = cmds.frameLayout(label="Load FK Ctrl")
|
||||
cmds.columnLayout(rowSpacing=2, adjustableColumn=True)
|
||||
|
||||
self._create_text_field_row('target_01', '< FK Joint Root')
|
||||
self._create_text_field_row('target_02', '< FK Joint Mid')
|
||||
self._create_text_field_row('target_03', '< FK Joint End')
|
||||
cmds.separator(style='in', height=5)
|
||||
self._create_text_field_row('target_04', '< FK Ctrl Root')
|
||||
self._create_text_field_row('target_05', '< FK Ctrl Mid')
|
||||
self._create_text_field_row('target_06', '< FK Ctrl End')
|
||||
|
||||
cmds.setParent('..')
|
||||
cmds.setParent('..')
|
||||
|
||||
# IK Controls frame
|
||||
ik_frame = cmds.frameLayout(label="Load IK Ctrl")
|
||||
cmds.columnLayout(rowSpacing=2, adjustableColumn=True)
|
||||
|
||||
self._create_text_field_row('target_07', '< IK Ctrl Root')
|
||||
self._create_text_field_row('target_08', '< IK Ctrl Pole')
|
||||
|
||||
cmds.setParent('..')
|
||||
cmds.setParent('..')
|
||||
|
||||
# Switch Control frame
|
||||
switch_frame = cmds.frameLayout(label="Load Switch Ctrl")
|
||||
cmds.columnLayout(rowSpacing=2, adjustableColumn=True)
|
||||
|
||||
self._create_text_field_row('target_09', '< Switch Ctrl')
|
||||
|
||||
# Special row for attribute selection
|
||||
cmds.rowLayout(numberOfColumns=2, adjustableColumn=1, columnWidth2=(45, 60))
|
||||
self.target_fields['target_10'] = cmds.textField(text='')
|
||||
cmds.button(width=120, label='< Switch Attr', command=lambda x: self.load_attr())
|
||||
cmds.setParent('..')
|
||||
|
||||
cmds.setParent('..')
|
||||
cmds.setParent('..')
|
||||
|
||||
# Execute button
|
||||
cmds.separator(style='in', height=5)
|
||||
cmds.button(height=32, label="<<< Build Seamless Switching >>>", command=lambda x: self.execute())
|
||||
|
||||
# Show window
|
||||
cmds.window(self.WINDOW_NAME, edit=True, height=self.WINDOW_HEIGHT)
|
||||
cmds.showWindow(self.WINDOW_NAME)
|
||||
|
||||
def _create_text_field_row(self, target_name, button_label):
|
||||
"""Create a text field row with button"""
|
||||
cmds.rowLayout(numberOfColumns=2, adjustableColumn=1, columnWidth2=(45, 60))
|
||||
self.target_fields[target_name] = cmds.textField(text=self.target_values[target_name])
|
||||
cmds.button(
|
||||
width=120,
|
||||
label=button_label,
|
||||
command=lambda x: self.set_text_field(target_name)
|
||||
)
|
||||
cmds.setParent('..')
|
||||
|
||||
def set_text_field(self, target_name):
|
||||
"""Set text field from selection"""
|
||||
selection = cmds.ls(selection=True)
|
||||
if selection:
|
||||
obj_name = selection[0]
|
||||
cmds.textField(self.target_fields[target_name], edit=True, text=obj_name)
|
||||
self.target_values[target_name] = obj_name
|
||||
|
||||
def load_attr(self):
|
||||
"""Load attribute from channel box selection"""
|
||||
try:
|
||||
# Get selected attributes from channel box
|
||||
attrs = cmds.channelBox('mainChannelBox', query=True, selectedMainAttributes=True)
|
||||
if not attrs:
|
||||
cmds.error("Select an attribute...")
|
||||
return
|
||||
|
||||
attr_name = attrs[0]
|
||||
cmds.textField(self.target_fields['target_10'], edit=True, text=attr_name)
|
||||
self.target_values['target_10'] = attr_name
|
||||
except Exception as e:
|
||||
cmds.warning("Failed to load attribute: {}".format(e))
|
||||
|
||||
def execute(self):
|
||||
"""Execute the seamless switching setup"""
|
||||
# Get values from text fields
|
||||
fk_joint_root = cmds.textField(self.target_fields['target_01'], query=True, text=True)
|
||||
fk_joint_mid = cmds.textField(self.target_fields['target_02'], query=True, text=True)
|
||||
fk_joint_end = cmds.textField(self.target_fields['target_03'], query=True, text=True)
|
||||
fk_ctrl_root = cmds.textField(self.target_fields['target_04'], query=True, text=True)
|
||||
fk_ctrl_mid = cmds.textField(self.target_fields['target_05'], query=True, text=True)
|
||||
fk_ctrl_end = cmds.textField(self.target_fields['target_06'], query=True, text=True)
|
||||
ik_ctrl_root = cmds.textField(self.target_fields['target_07'], query=True, text=True)
|
||||
ik_ctrl_pole = cmds.textField(self.target_fields['target_08'], query=True, text=True)
|
||||
switch_ctrl = cmds.textField(self.target_fields['target_09'], query=True, text=True)
|
||||
switch_attr = cmds.textField(self.target_fields['target_10'], query=True, text=True)
|
||||
|
||||
# Call the seamless switching function
|
||||
# Note: IK joints use FK joints for reference (same as MEL version)
|
||||
seamless_switching(
|
||||
fk_joint_root, fk_joint_mid, fk_joint_end,
|
||||
fk_ctrl_root, fk_ctrl_mid, fk_ctrl_end,
|
||||
fk_joint_root, fk_joint_mid, fk_joint_end, # IK joints = FK joints
|
||||
ik_ctrl_root, ik_ctrl_pole,
|
||||
switch_ctrl, switch_attr
|
||||
)
|
||||
|
||||
def adv_build(self):
|
||||
"""Build ADV rig presets"""
|
||||
# Right arm
|
||||
seamless_switching(
|
||||
"FKXShoulder_R", "FKXElbow_R", "FKXWrist_R",
|
||||
"FKShoulder_R", "FKElbow_R", "FKWrist_R",
|
||||
"IKXShoulder_R", "IKXElbow_R", "IKXWrist_R",
|
||||
"IKArm_R", "PoleArm_R",
|
||||
"FKIKArm_R", "FKIKBlend"
|
||||
)
|
||||
|
||||
# Left arm
|
||||
seamless_switching(
|
||||
"FKXShoulder_L", "FKXElbow_L", "FKXWrist_L",
|
||||
"FKShoulder_L", "FKElbow_L", "FKWrist_L",
|
||||
"IKXShoulder_L", "IKXElbow_L", "IKXWrist_L",
|
||||
"IKArm_L", "PoleArm_L",
|
||||
"FKIKArm_L", "FKIKBlend"
|
||||
)
|
||||
|
||||
# Right leg
|
||||
seamless_switching(
|
||||
"FKXHip_R", "FKXKnee_R", "FKXAnkle_R",
|
||||
"FKHip_R", "FKKnee_R", "FKAnkle_R",
|
||||
"IKXHip_R", "IKXKnee_R", "IKXAnkle_R",
|
||||
"IKLeg_R", "PoleLeg_R",
|
||||
"FKIKLeg_R", "FKIKBlend"
|
||||
)
|
||||
|
||||
# Left leg
|
||||
seamless_switching(
|
||||
"FKXHip_L", "FKXKnee_L", "FKXAnkle_L",
|
||||
"FKHip_L", "FKKnee_L", "FKAnkle_L",
|
||||
"IKXHip_L", "IKXKnee_L", "IKXAnkle_L",
|
||||
"IKLeg_L", "PoleLeg_L",
|
||||
"FKIKLeg_L", "FKIKBlend"
|
||||
)
|
||||
|
||||
def empty_fields(self):
|
||||
"""Clear all text fields"""
|
||||
for target_name in self.target_values.keys():
|
||||
self.target_values[target_name] = ''
|
||||
if target_name in self.target_fields:
|
||||
cmds.textField(self.target_fields[target_name], edit=True, text='')
|
||||
|
||||
|
||||
def seamless_switching(
|
||||
fk_joint_root, fk_joint_mid, fk_joint_end,
|
||||
fk_ctrl_root, fk_ctrl_mid, fk_ctrl_end,
|
||||
ik_joint_root, ik_joint_mid, ik_joint_end,
|
||||
ik_ctrl_root, ik_ctrl_pole,
|
||||
switch_ctrl, switch_attr
|
||||
):
|
||||
"""
|
||||
Setup seamless IKFK switching
|
||||
|
||||
Args:
|
||||
fk_joint_root: FK joint root
|
||||
fk_joint_mid: FK joint mid
|
||||
fk_joint_end: FK joint end
|
||||
fk_ctrl_root: FK control root
|
||||
fk_ctrl_mid: FK control mid
|
||||
fk_ctrl_end: FK control end
|
||||
ik_joint_root: IK joint root
|
||||
ik_joint_mid: IK joint mid
|
||||
ik_joint_end: IK joint end
|
||||
ik_ctrl_root: IK control root
|
||||
ik_ctrl_pole: IK pole vector control
|
||||
switch_ctrl: Switch control
|
||||
switch_attr: Switch attribute name
|
||||
"""
|
||||
# Create locator for storing data
|
||||
locator_name = "{}_Switch_Locator".format(switch_ctrl)
|
||||
|
||||
if not cmds.objExists(locator_name):
|
||||
locator_shape = cmds.createNode('locator', name=locator_name)
|
||||
locator_transform = cmds.listRelatives(locator_shape, parent=True)[0]
|
||||
cmds.parent(locator_shape, switch_ctrl, shape=True, add=True)
|
||||
cmds.delete(locator_transform)
|
||||
cmds.setAttr("{}.visibility".format(locator_name), 0)
|
||||
|
||||
# Add IKFK_Seamless attribute if it doesn't exist
|
||||
if not cmds.objExists("{}.IKFK_Seamless".format(switch_ctrl)):
|
||||
cmds.addAttr(switch_ctrl, longName="IKFK_Seamless", attributeType="enum", enumName="IK:FK:", keyable=False)
|
||||
cmds.setAttr("{}.IKFK_Seamless".format(switch_ctrl), channelBox=True)
|
||||
|
||||
# Add Location attribute to all objects
|
||||
objs = [
|
||||
fk_joint_root, fk_joint_mid, fk_joint_end,
|
||||
fk_ctrl_root, fk_ctrl_mid, fk_ctrl_end,
|
||||
ik_joint_root, ik_joint_mid, ik_joint_end,
|
||||
ik_ctrl_root, ik_ctrl_pole, switch_ctrl
|
||||
]
|
||||
|
||||
for obj in objs:
|
||||
# Check if object name is not empty and exists
|
||||
if obj and cmds.objExists(obj):
|
||||
if not cmds.objExists("{}.Location".format(obj)):
|
||||
cmds.addAttr(obj, longName="Location", dataType="string", keyable=True)
|
||||
else:
|
||||
cmds.warning("Object does not exist or is empty: {}".format(obj))
|
||||
|
||||
# Add attributes to locator and connect
|
||||
attrs_data = {
|
||||
"IKFK_Seamless_Switching": locator_name,
|
||||
"FK_Joint_Root": fk_joint_root,
|
||||
"FK_Joint_Mid": fk_joint_mid,
|
||||
"FK_Joint_End": fk_joint_end,
|
||||
"FK_Ctrl_Root": fk_ctrl_root,
|
||||
"FK_Ctrl_Mid": fk_ctrl_mid,
|
||||
"FK_Ctrl_End": fk_ctrl_end,
|
||||
"IK_Joint_Root": ik_joint_root,
|
||||
"IK_Joint_Mid": ik_joint_mid,
|
||||
"IK_Joint_End": ik_joint_end,
|
||||
"IK_Ctrl_Root": ik_ctrl_root,
|
||||
"IK_Ctrl_Pole": ik_ctrl_pole,
|
||||
"Switch_Ctrl": switch_ctrl,
|
||||
"Switch_Attr": switch_attr
|
||||
}
|
||||
|
||||
for attr_name, attr_value in attrs_data.items():
|
||||
if not cmds.objExists("{}.{}".format(locator_name, attr_name)):
|
||||
cmds.addAttr(locator_name, longName=attr_name, dataType="string", keyable=True)
|
||||
|
||||
cmds.setAttr("{}.{}".format(locator_name, attr_name), attr_value, type="string")
|
||||
|
||||
# Connect to Location attribute (except for special attributes)
|
||||
if attr_name not in ["IKFK_Seamless_Switching", "Switch_Attr"]:
|
||||
target_obj = attr_value
|
||||
if cmds.objExists("{}.Location".format(target_obj)):
|
||||
if not cmds.isConnected("{}.{}".format(locator_name, attr_name), "{}.Location".format(target_obj)):
|
||||
cmds.connectAttr("{}.{}".format(locator_name, attr_name), "{}.Location".format(target_obj), force=True)
|
||||
|
||||
# Create switching script
|
||||
create_switching_script()
|
||||
|
||||
# Create script job for this control
|
||||
create_script_job(switch_ctrl, locator_name)
|
||||
|
||||
print("IKFK Seamless Switching setup completed for {}".format(switch_ctrl))
|
||||
|
||||
|
||||
def create_switching_script():
|
||||
"""Create the global switching script"""
|
||||
script_node_name = "SG_IKFK_Switching_Script"
|
||||
|
||||
# MEL script for switching logic
|
||||
mel_script = '''
|
||||
global proc sg_switching (string $Switch)
|
||||
{
|
||||
int $state_tmp;
|
||||
if (!`optionVar -ex $Switch`){
|
||||
$state_tmp = 0;
|
||||
}
|
||||
$state_tmp = `optionVar -q $Switch`;
|
||||
string $tmp[] = `listConnections ($Switch+".Switch_Ctrl")`;
|
||||
string $Switch_Ctrl = $tmp[0];
|
||||
int $state=`getAttr ($Switch_Ctrl+".IKFK_Seamless")`;
|
||||
if($state_tmp == $state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string $sel[] = `ls -sl`;
|
||||
string $tmp01[] = `listConnections ($Switch+".FK_Joint_Root")`;
|
||||
string $tmp02[] = `listConnections ($Switch+".FK_Joint_Mid")`;
|
||||
string $tmp03[] = `listConnections ($Switch+".FK_Joint_End")`;
|
||||
string $tmp04[] = `listConnections ($Switch+".FK_Ctrl_Root")`;
|
||||
string $tmp05[] = `listConnections ($Switch+".FK_Ctrl_Mid")`;
|
||||
string $tmp06[] = `listConnections ($Switch+".FK_Ctrl_End")`;
|
||||
string $tmp07[] = `listConnections ($Switch+".IK_Joint_Root")`;
|
||||
string $tmp08[] = `listConnections ($Switch+".IK_Joint_Mid")`;
|
||||
string $tmp09[] = `listConnections ($Switch+".IK_Joint_End")`;
|
||||
string $tmp10[] = `listConnections ($Switch+".IK_Ctrl_Root")`;
|
||||
string $tmp11[] = `listConnections ($Switch+".IK_Ctrl_Pole")`;
|
||||
|
||||
string $FK_Joint_Root = $tmp01[0];
|
||||
string $FK_Joint_Mid = $tmp02[0];
|
||||
string $FK_Joint_End = $tmp03[0];
|
||||
string $FK_Ctrl_Root = $tmp04[0];
|
||||
string $FK_Ctrl_Mid = $tmp05[0];
|
||||
string $FK_Ctrl_End = $tmp06[0];
|
||||
string $IK_Joint_Root = $tmp07[0];
|
||||
string $IK_Joint_Mid = $tmp08[0];
|
||||
string $IK_Joint_End = $tmp09[0];
|
||||
string $IK_Ctrl_Root = $tmp10[0];
|
||||
string $IK_Ctrl_Pole = $tmp11[0];
|
||||
|
||||
string $Switch_Attr = `getAttr ($Switch+".Switch_Attr")`;
|
||||
|
||||
int $min = `addAttr -q -min ($Switch_Ctrl+"."+$Switch_Attr)`;
|
||||
int $max = `addAttr -q -max ($Switch_Ctrl+"."+$Switch_Attr)`;
|
||||
int $time=`currentTime -q`;
|
||||
setKeyframe $FK_Ctrl_Root;
|
||||
setKeyframe $FK_Ctrl_Mid;
|
||||
setKeyframe $FK_Ctrl_End;
|
||||
setKeyframe $IK_Ctrl_Root;
|
||||
setKeyframe $IK_Ctrl_Pole;
|
||||
if($state==0){
|
||||
string $tempGroup_A = `group -em`;
|
||||
string $tempGroup_B = `group -em`;
|
||||
delete `parentConstraint -weight 1 $IK_Joint_End $tempGroup_A`;
|
||||
delete `parentConstraint -weight 1 $IK_Joint_End $tempGroup_B`;
|
||||
delete `orientConstraint -offset 0 0 0 -weight 1 $IK_Ctrl_Root $tempGroup_B`;
|
||||
parent $tempGroup_B $tempGroup_A;
|
||||
delete `parentConstraint -weight 1 $FK_Joint_End $tempGroup_A`;
|
||||
string $con_A[] = `parentConstraint -weight 1 $tempGroup_B $IK_Ctrl_Root`;
|
||||
setKeyframe $IK_Ctrl_Root;
|
||||
delete $con_A;
|
||||
string $con_B[] = `pointConstraint -offset 0 0 0 -weight 1 $FK_Joint_Mid $IK_Ctrl_Pole`;
|
||||
setKeyframe $IK_Ctrl_Pole;
|
||||
delete $con_B;
|
||||
setAttr ($Switch_Ctrl+"."+$Switch_Attr) $min;
|
||||
setKeyframe -t ($time-1) -at $Switch_Attr $Switch_Ctrl;
|
||||
setAttr ($Switch_Ctrl+"."+$Switch_Attr) $max;
|
||||
setKeyframe -t $time -at $Switch_Attr $Switch_Ctrl;
|
||||
delete $tempGroup_A;
|
||||
}
|
||||
if($state==1){
|
||||
string $con_A[] = `parentConstraint -weight 1 $IK_Joint_Root $FK_Ctrl_Root`;
|
||||
setKeyframe $FK_Ctrl_Root;
|
||||
delete $con_A;
|
||||
string $con_B[] = `parentConstraint -weight 1 $IK_Joint_Mid $FK_Ctrl_Mid`;
|
||||
setKeyframe $FK_Ctrl_Mid;
|
||||
delete $con_B;
|
||||
string $con_C[] = `parentConstraint -weight 1 $IK_Joint_End $FK_Ctrl_End`;
|
||||
setKeyframe $FK_Ctrl_End;
|
||||
delete $con_C;
|
||||
setAttr ($Switch_Ctrl+"."+$Switch_Attr) $max;
|
||||
setKeyframe -t ($time-1) -at $Switch_Attr $Switch_Ctrl;
|
||||
setAttr ($Switch_Ctrl+"."+$Switch_Attr) $min;
|
||||
setKeyframe -t $time -at $Switch_Attr $Switch_Ctrl;
|
||||
}
|
||||
optionVar -iv $Switch $state;
|
||||
select -r $sel;
|
||||
}
|
||||
'''
|
||||
|
||||
if not cmds.objExists(script_node_name):
|
||||
cmds.scriptNode(beforeScript=mel_script, name=script_node_name)
|
||||
else:
|
||||
cmds.scriptNode(script_node_name, edit=True, beforeScript=mel_script)
|
||||
|
||||
cmds.setAttr("{}.scriptType".format(script_node_name), 1)
|
||||
cmds.scriptNode(script_node_name, executeBefore=True)
|
||||
|
||||
|
||||
def create_script_job(switch_ctrl, locator_name):
|
||||
"""Create script job for automatic switching"""
|
||||
script_node_name = "{}_Switching_Script".format(switch_ctrl)
|
||||
|
||||
# MEL script for script job creation
|
||||
mel_script = '''
|
||||
string $Locator_all[]=`ls -typ "locator"`;
|
||||
for($Locator in $Locator_all){{
|
||||
if(`objExists ($Locator+".IKFK_Seamless_Switching")`){{
|
||||
string $tmp=`getAttr ($Locator+".IKFK_Seamless_Switching")`;
|
||||
if($tmp=="{0}"){{
|
||||
string $Switch[]=`listConnections ($Locator+".Switch_Ctrl")`;
|
||||
scriptJob -ac ($Switch[0]+".IKFK_Seamless") ("sg_switching "+$Locator) -kws;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
'''.format(locator_name)
|
||||
|
||||
if not cmds.objExists(script_node_name):
|
||||
cmds.scriptNode(beforeScript=mel_script, name=script_node_name)
|
||||
else:
|
||||
cmds.scriptNode(script_node_name, edit=True, beforeScript=mel_script)
|
||||
|
||||
cmds.setAttr("{}.scriptType".format(script_node_name), 1)
|
||||
cmds.scriptNode(script_node_name, executeBefore=True)
|
||||
|
||||
|
||||
def show():
|
||||
"""Main entry point to show the UI"""
|
||||
ui = IKFKSwitchUI()
|
||||
ui.create_ui()
|
||||
|
||||
|
||||
# For backwards compatibility
|
||||
def start():
|
||||
"""Alternative entry point"""
|
||||
show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
show()
|
||||
@@ -69,7 +69,7 @@ global proc shelf_Nexus_Animation () {
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "import animation_tools.ikfx_switch\nanimation_tools.ikfx_switch.show()"
|
||||
-command "from animation_tools import ikfk_switch\nikfk_switch.IKFK_Switch_UI()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
|
||||
Reference in New Issue
Block a user