Files
Nexus/2025/scripts/animation_tools/ikfk_switch.py
2025-12-01 03:56:56 +08:00

603 lines
24 KiB
Python

#!/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()