From e0d4d0c364255e9e5b3f4901e0408324ef670abb Mon Sep 17 00:00:00 2001 From: jeffreytsai1004 Date: Mon, 1 Dec 2025 03:56:56 +0800 Subject: [PATCH] Update --- 2023/scripts/animation_tools/ikfk_switch.py | 603 ++++++++++++++++++++ 2023/scripts/animation_tools/ikfx_switch.py | 461 --------------- 2023/shelves/shelf_Nexus_Animation.mel | 2 +- 2025/scripts/animation_tools/ikfk_switch.py | 603 ++++++++++++++++++++ 2025/scripts/animation_tools/ikfx_switch.py | 461 --------------- 2025/shelves/shelf_Nexus_Animation.mel | 2 +- 6 files changed, 1208 insertions(+), 924 deletions(-) create mode 100644 2023/scripts/animation_tools/ikfk_switch.py delete mode 100644 2023/scripts/animation_tools/ikfx_switch.py create mode 100644 2025/scripts/animation_tools/ikfk_switch.py delete mode 100644 2025/scripts/animation_tools/ikfx_switch.py 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