diff --git a/2023/scripts/animation_tools/ikfk_switch.py b/2023/scripts/animation_tools/ikfk_switch.py
new file mode 100644
index 0000000..f0cb555
--- /dev/null
+++ b/2023/scripts/animation_tools/ikfk_switch.py
@@ -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: {}'.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()
\ No newline at end of file
diff --git a/2023/scripts/animation_tools/ikfx_switch.py b/2023/scripts/animation_tools/ikfx_switch.py
deleted file mode 100644
index 06a7685..0000000
--- a/2023/scripts/animation_tools/ikfx_switch.py
+++ /dev/null
@@ -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()
diff --git a/2023/shelves/shelf_Nexus_Animation.mel b/2023/shelves/shelf_Nexus_Animation.mel
index 4d00340..5ac0b6c 100644
--- a/2023/shelves/shelf_Nexus_Animation.mel
+++ b/2023/shelves/shelf_Nexus_Animation.mel
@@ -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
diff --git a/2025/scripts/animation_tools/ikfk_switch.py b/2025/scripts/animation_tools/ikfk_switch.py
new file mode 100644
index 0000000..f0cb555
--- /dev/null
+++ b/2025/scripts/animation_tools/ikfk_switch.py
@@ -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: {}'.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()
\ No newline at end of file
diff --git a/2025/scripts/animation_tools/ikfx_switch.py b/2025/scripts/animation_tools/ikfx_switch.py
deleted file mode 100644
index 06a7685..0000000
--- a/2025/scripts/animation_tools/ikfx_switch.py
+++ /dev/null
@@ -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()
diff --git a/2025/shelves/shelf_Nexus_Animation.mel b/2025/shelves/shelf_Nexus_Animation.mel
index 05699fc..e1ef2ff 100644
--- a/2025/shelves/shelf_Nexus_Animation.mel
+++ b/2025/shelves/shelf_Nexus_Animation.mel
@@ -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