diff --git a/2023/icons/ikfk_switcher.png b/2023/icons/ikfk_switcher.png
new file mode 100644
index 0000000..b8211f0
Binary files /dev/null and b/2023/icons/ikfk_switcher.png differ
diff --git a/2023/scripts/animation_tools/ikfk_switcher.py b/2023/scripts/animation_tools/ikfk_switcher.py
new file mode 100644
index 0000000..6754314
--- /dev/null
+++ b/2023/scripts/animation_tools/ikfk_switcher.py
@@ -0,0 +1,706 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+IKFK Seamless Switching Tool V1.0
+IKFK无缝切换工具
+使用scriptJob实现实时切换
+"""
+
+import maya.cmds as cmds
+import maya.mel as mel
+
+
+class IKFKSwitcher(object):
+ """IKFK无缝切换工具主类"""
+
+ def __init__(self):
+ self.window_name = "ikfk_switcher_window"
+ self.window_title = "IKFK Seamless Switching Tool V1.0"
+
+ # 存储控制器和关节
+ self.joints = {
+ 'shoulder': None,
+ 'elbow': None,
+ 'wrist': None
+ }
+
+ self.fk_ctrls = {
+ 'shoulder': None,
+ 'elbow': None,
+ 'wrist': None
+ }
+
+ self.ik_ctrls = {
+ 'wrist': None,
+ 'pole': None
+ }
+
+ self.switch_ctrl = None
+ self.switch_attr = None
+
+ # scriptJob ID列表
+ self.script_jobs = []
+
+ # UI控件引用
+ self.ui_elements = {}
+
+ def create_ui(self):
+ """创建UI界面"""
+ # 如果窗口存在则删除
+ if cmds.window(self.window_name, exists=True):
+ cmds.deleteUI(self.window_name)
+
+ # 创建窗口
+ self.window_name = cmds.window(
+ self.window_name,
+ title=self.window_title,
+ widthHeight=(280, 650),
+ sizeable=True
+ )
+
+ # 主布局
+ main_layout = cmds.columnLayout(adjustableColumn=True, rowSpacing=5)
+
+ # 标题栏
+ cmds.rowLayout(numberOfColumns=3, adjustableColumn=2, columnAttach=[(1, 'left', 5)])
+ cmds.text(label="IKFK Seamless Switching Tool V1.0", font="boldLabelFont")
+ cmds.button(label="?", width=25, height=25, command=self.show_help)
+ cmds.button(label="X", width=25, height=25,
+ backgroundColor=[0.8, 0.3, 0.3],
+ command=lambda x: cmds.deleteUI(self.window_name))
+ cmds.setParent('..')
+
+ cmds.separator(height=10, style='in')
+
+ # Edit菜单
+ cmds.frameLayout(label="Edit", collapsable=True, collapse=False,
+ borderStyle='etchedIn', marginWidth=5, marginHeight=5)
+ cmds.columnLayout(adjustableColumn=True, rowSpacing=3)
+
+ cmds.button(label="<<< ADV Build >>>", height=25,
+ command=self.adv_build,
+ backgroundColor=[0.4, 0.4, 0.4])
+ cmds.button(label="<<< Empty >>>", height=25,
+ command=self.empty_fields,
+ backgroundColor=[0.4, 0.4, 0.4])
+
+ cmds.setParent('..')
+ cmds.setParent('..')
+
+ cmds.separator(height=10, style='in')
+
+ # Load Joint部分
+ cmds.frameLayout(label="Load Joint", collapsable=False,
+ borderStyle='etchedIn', marginWidth=5, marginHeight=5)
+ cmds.columnLayout(adjustableColumn=True, rowSpacing=3)
+
+ self.ui_elements['joint_shoulder_field'] = self._create_load_field("Joint Shoulder", self.load_joint_shoulder)
+ self.ui_elements['joint_elbow_field'] = self._create_load_field("Joint Elbow", self.load_joint_elbow)
+ self.ui_elements['joint_wrist_field'] = self._create_load_field("Joint Wrist", self.load_joint_wrist)
+
+ cmds.setParent('..')
+ cmds.setParent('..')
+
+ cmds.separator(height=5)
+
+ # Load FK Ctrl部分
+ cmds.frameLayout(label="Load FK Ctrl", collapsable=False,
+ borderStyle='etchedIn', marginWidth=5, marginHeight=5)
+ cmds.columnLayout(adjustableColumn=True, rowSpacing=3)
+
+ self.ui_elements['fk_shoulder_field'] = self._create_load_field("FK Shoulder", self.load_fk_shoulder)
+ self.ui_elements['fk_elbow_field'] = self._create_load_field("FK Elbow", self.load_fk_elbow)
+ self.ui_elements['fk_wrist_field'] = self._create_load_field("FK Wrist", self.load_fk_wrist)
+
+ cmds.setParent('..')
+ cmds.setParent('..')
+
+ cmds.separator(height=5)
+
+ # Load IK Ctrl部分
+ cmds.frameLayout(label="Load IK Ctrl", collapsable=False,
+ borderStyle='etchedIn', marginWidth=5, marginHeight=5)
+ cmds.columnLayout(adjustableColumn=True, rowSpacing=3)
+
+ self.ui_elements['ik_wrist_field'] = self._create_load_field("IK Wrist", self.load_ik_wrist)
+ self.ui_elements['ik_pole_field'] = self._create_load_field("IK Pole", self.load_ik_pole)
+
+ cmds.setParent('..')
+ cmds.setParent('..')
+
+ cmds.separator(height=5)
+
+ # Load Switch Ctrl部分
+ cmds.frameLayout(label="Load Switch Ctrl", collapsable=False,
+ borderStyle='etchedIn', marginWidth=5, marginHeight=5)
+ cmds.columnLayout(adjustableColumn=True, rowSpacing=3)
+
+ self.ui_elements['switch_ctrl_field'] = self._create_load_field("Switch Ctrl", self.load_switch_ctrl)
+ self.ui_elements['switch_attr_field'] = self._create_load_field("Switch Attr", self.load_switch_attr)
+
+ cmds.setParent('..')
+ cmds.setParent('..')
+
+ cmds.separator(height=10, style='in')
+
+ # 构建按钮
+ cmds.button(label=">>> Build Seamless Switching >>>",
+ height=35,
+ backgroundColor=[0.4, 0.7, 0.4],
+ command=self.build_seamless_switching)
+
+ cmds.separator(height=5)
+
+ # 显示窗口
+ cmds.showWindow(self.window_name)
+
+ def _create_load_field(self, label, command):
+ """创建加载字段的辅助方法"""
+ cmds.rowLayout(numberOfColumns=2, adjustableColumn=1, columnAttach=[(2, 'right', 5)])
+ text_field = cmds.textField(editable=False, backgroundColor=[0.2, 0.2, 0.2])
+ cmds.button(label="<<< {} ".format(label), width=120, command=command)
+ cmds.setParent('..')
+ return text_field
+
+ def load_joint_shoulder(self, *args):
+ """加载肩部关节"""
+ sel = cmds.ls(selection=True)
+ if sel:
+ self.joints['shoulder'] = sel[0]
+ cmds.textField(self.ui_elements['joint_shoulder_field'], edit=True, text=sel[0])
+ print("Loaded Joint Shoulder: {}".format(sel[0]))
+ else:
+ cmds.warning("Please select Joint Shoulder")
+
+ def load_joint_elbow(self, *args):
+ """加载肘部关节"""
+ sel = cmds.ls(selection=True)
+ if sel:
+ self.joints['elbow'] = sel[0]
+ cmds.textField(self.ui_elements['joint_elbow_field'], edit=True, text=sel[0])
+ print("Loaded Joint Elbow: {}".format(sel[0]))
+ else:
+ cmds.warning("Please select Joint Elbow")
+
+ def load_joint_wrist(self, *args):
+ """加载腕部关节"""
+ sel = cmds.ls(selection=True)
+ if sel:
+ self.joints['wrist'] = sel[0]
+ cmds.textField(self.ui_elements['joint_wrist_field'], edit=True, text=sel[0])
+ print("Loaded Joint Wrist: {}".format(sel[0]))
+ else:
+ cmds.warning("Please select Joint Wrist")
+
+ def load_fk_shoulder(self, *args):
+ """加载FK肩部控制器"""
+ sel = cmds.ls(selection=True)
+ if sel:
+ self.fk_ctrls['shoulder'] = sel[0]
+ cmds.textField(self.ui_elements['fk_shoulder_field'], edit=True, text=sel[0])
+ print("Loaded FK Shoulder: {}".format(sel[0]))
+ else:
+ cmds.warning("Please select FK Shoulder control")
+
+ def load_fk_elbow(self, *args):
+ """加载FK肘部控制器"""
+ sel = cmds.ls(selection=True)
+ if sel:
+ self.fk_ctrls['elbow'] = sel[0]
+ cmds.textField(self.ui_elements['fk_elbow_field'], edit=True, text=sel[0])
+ print("Loaded FK Elbow: {}".format(sel[0]))
+ else:
+ cmds.warning("Please select FK Elbow control")
+
+ def load_fk_wrist(self, *args):
+ """加载FK腕部控制器"""
+ sel = cmds.ls(selection=True)
+ if sel:
+ self.fk_ctrls['wrist'] = sel[0]
+ cmds.textField(self.ui_elements['fk_wrist_field'], edit=True, text=sel[0])
+ print("Loaded FK Wrist: {}".format(sel[0]))
+ else:
+ cmds.warning("Please select FK Wrist control")
+
+ def load_ik_wrist(self, *args):
+ """加载IK腕部控制器"""
+ sel = cmds.ls(selection=True)
+ if sel:
+ self.ik_ctrls['wrist'] = sel[0]
+ cmds.textField(self.ui_elements['ik_wrist_field'], edit=True, text=sel[0])
+ print("Loaded IK Wrist: {}".format(sel[0]))
+ else:
+ cmds.warning("Please select IK Wrist control")
+
+ def load_ik_pole(self, *args):
+ """加载IK极向量控制器"""
+ sel = cmds.ls(selection=True)
+ if sel:
+ self.ik_ctrls['pole'] = sel[0]
+ cmds.textField(self.ui_elements['ik_pole_field'], edit=True, text=sel[0])
+ print("Loaded IK Pole: {}".format(sel[0]))
+ else:
+ cmds.warning("Please select IK Pole control")
+
+ def load_switch_ctrl(self, *args):
+ """加载切换控制器"""
+ sel = cmds.ls(selection=True)
+ if sel:
+ self.switch_ctrl = sel[0]
+ cmds.textField(self.ui_elements['switch_ctrl_field'], edit=True, text=sel[0])
+ print("Loaded Switch Control: {}".format(sel[0]))
+ else:
+ cmds.warning("Please select Switch control")
+
+ def load_switch_attr(self, *args):
+ """加载切换属性"""
+ sel = cmds.ls(selection=True)
+ if sel and self.switch_ctrl:
+ # 获取选中的通道
+ channels = cmds.channelBox('mainChannelBox', query=True, selectedMainAttributes=True)
+ if channels:
+ self.switch_attr = channels[0]
+ cmds.textField(self.ui_elements['switch_attr_field'], edit=True, text=self.switch_attr)
+ print("Loaded Switch Attribute: {}".format(self.switch_attr))
+ else:
+ cmds.warning("Please select an attribute in the Channel Box")
+ else:
+ cmds.warning("Please load Switch Control first and select an attribute")
+
+ def build_seamless_switching(self, *args):
+ """构建无缝切换系统"""
+ # 验证所有必需的控制器和关节是否已加载
+ if not self._validate_inputs():
+ return
+
+ # 清除现有的scriptJob
+ self.clear_script_jobs()
+
+ # 创建scriptJob来监听切换属性的变化
+ if self.switch_ctrl and self.switch_attr:
+ attr_full_name = "{}.{}".format(self.switch_ctrl, self.switch_attr)
+
+ # 创建属性变化监听
+ job_id = cmds.scriptJob(
+ attributeChange=[attr_full_name, self.on_switch_changed],
+ killWithScene=True,
+ protected=True
+ )
+ self.script_jobs.append(job_id)
+
+ print("Created scriptJob (ID: {}) for attribute: {}".format(job_id, attr_full_name))
+ cmds.inViewMessage(
+ amg='Seamless switching built successfully!',
+ pos='midCenter',
+ fade=True,
+ fadeStayTime=2000,
+ fadeOutTime=500
+ )
+ else:
+ cmds.warning("Switch control or attribute not loaded")
+
+ def _validate_inputs(self):
+ """验证所有输入是否有效"""
+ # 检查关节
+ for key, joint in self.joints.items():
+ if not joint or not cmds.objExists(joint):
+ cmds.warning("Joint {} not loaded or doesn't exist".format(key))
+ return False
+
+ # 检查FK控制器
+ for key, ctrl in self.fk_ctrls.items():
+ if not ctrl or not cmds.objExists(ctrl):
+ cmds.warning("FK control {} not loaded or doesn't exist".format(key))
+ return False
+
+ # 检查IK控制器
+ for key, ctrl in self.ik_ctrls.items():
+ if not ctrl or not cmds.objExists(ctrl):
+ cmds.warning("IK control {} not loaded or doesn't exist".format(key))
+ return False
+
+ # 检查切换控制器
+ if not self.switch_ctrl or not cmds.objExists(self.switch_ctrl):
+ cmds.warning("Switch control not loaded or doesn't exist")
+ return False
+
+ if not self.switch_attr:
+ cmds.warning("Switch attribute not loaded")
+ return False
+
+ return True
+
+ def on_switch_changed(self):
+ """当切换属性改变时调用"""
+ if not self.switch_ctrl or not self.switch_attr:
+ return
+
+ # 获取切换值 (假设0=FK, 1=IK)
+ switch_value = cmds.getAttr("{}.{}".format(self.switch_ctrl, self.switch_attr))
+
+ # 根据切换值进行匹配
+ if switch_value == 0:
+ # 切换到FK - 匹配FK到IK
+ self.match_fk_to_ik()
+ elif switch_value == 1:
+ # 切换到IK - 匹配IK到FK
+ self.match_ik_to_fk()
+
+ def match_fk_to_ik(self):
+ """匹配FK控制器到IK位置"""
+ try:
+ # 匹配FK肩部到关节
+ if self.fk_ctrls['shoulder'] and self.joints['shoulder']:
+ cmds.matchTransform(self.fk_ctrls['shoulder'], self.joints['shoulder'],
+ position=False, rotation=True)
+
+ # 匹配FK肘部到关节
+ if self.fk_ctrls['elbow'] and self.joints['elbow']:
+ cmds.matchTransform(self.fk_ctrls['elbow'], self.joints['elbow'],
+ position=False, rotation=True)
+
+ # 匹配FK腕部到关节
+ if self.fk_ctrls['wrist'] and self.joints['wrist']:
+ cmds.matchTransform(self.fk_ctrls['wrist'], self.joints['wrist'],
+ position=False, rotation=True)
+
+ print("Matched FK to IK")
+ except Exception as e:
+ cmds.warning("Error matching FK to IK: {}".format(str(e)))
+
+ def match_ik_to_fk(self):
+ """匹配IK控制器到FK位置"""
+ try:
+ # 匹配IK腕部到关节
+ if self.ik_ctrls['wrist'] and self.joints['wrist']:
+ cmds.matchTransform(self.ik_ctrls['wrist'], self.joints['wrist'],
+ position=True, rotation=True)
+
+ # 计算并设置极向量位置
+ if self.ik_ctrls['pole'] and all(self.joints.values()):
+ pole_pos = self._calculate_pole_vector_position()
+ if pole_pos:
+ cmds.xform(self.ik_ctrls['pole'], worldSpace=True,
+ translation=pole_pos)
+
+ print("Matched IK to FK")
+ except Exception as e:
+ cmds.warning("Error matching IK to FK: {}".format(str(e)))
+
+ def _calculate_pole_vector_position(self):
+ """计算极向量位置"""
+ try:
+ # 获取三个关节的世界坐标
+ start_pos = cmds.xform(self.joints['shoulder'], query=True,
+ worldSpace=True, translation=True)
+ mid_pos = cmds.xform(self.joints['elbow'], query=True,
+ worldSpace=True, translation=True)
+ end_pos = cmds.xform(self.joints['wrist'], query=True,
+ worldSpace=True, translation=True)
+
+ # 使用向量计算极向量位置
+ import maya.OpenMaya as om
+
+ start_vec = om.MVector(start_pos[0], start_pos[1], start_pos[2])
+ mid_vec = om.MVector(mid_pos[0], mid_pos[1], mid_pos[2])
+ end_vec = om.MVector(end_pos[0], end_pos[1], end_pos[2])
+
+ # 计算从起点到终点的向量
+ start_to_end = end_vec - start_vec
+ start_to_mid = mid_vec - start_vec
+
+ # 投影
+ scale = (start_to_end * start_to_mid) / (start_to_end * start_to_end)
+ proj = start_to_end * scale + start_vec
+
+ # 计算极向量方向
+ pole_dir = mid_vec - proj
+ pole_dir.normalize()
+
+ # 计算链长度
+ length = (mid_vec - start_vec).length() + (end_vec - mid_vec).length()
+
+ # 极向量位置
+ pole_pos = mid_vec + pole_dir * length
+
+ return [pole_pos.x, pole_pos.y, pole_pos.z]
+ except Exception as e:
+ cmds.warning("Error calculating pole vector position: {}".format(str(e)))
+ return None
+
+ def clear_script_jobs(self):
+ """清除所有scriptJob"""
+ for job_id in self.script_jobs:
+ if cmds.scriptJob(exists=job_id):
+ cmds.scriptJob(kill=job_id, force=True)
+ print("Killed scriptJob ID: {}".format(job_id))
+ self.script_jobs = []
+
+ def adv_build(self, *args):
+ """高级构建功能 - 专门适配AdvanceSkeleton骨骼"""
+ # 获取当前选择
+ selection = cmds.ls(selection=True)
+ if not selection:
+ cmds.warning("Please select an AdvanceSkeleton control first")
+ return
+
+ # 尝试自动检测AdvanceSkeleton的IKFK_Seamless属性
+ ctrl = selection[0]
+
+ # 检查是否有IKFK_Seamless属性
+ if cmds.attributeQuery('IKFK_Seamless', node=ctrl, exists=True):
+ # 自动设置切换控制器和属性
+ self.switch_ctrl = ctrl
+ self.switch_attr = 'IKFK_Seamless'
+ cmds.textField(self.ui_elements['switch_ctrl_field'], edit=True, text=ctrl)
+ cmds.textField(self.ui_elements['switch_attr_field'], edit=True, text='IKFK_Seamless')
+
+ # 尝试自动查找相关的FK和IK控制器
+ self._auto_detect_adv_skeleton_controls(ctrl)
+
+ # 提示用户
+ result = cmds.confirmDialog(
+ title='AdvanceSkeleton Detected',
+ message='Detected AdvanceSkeleton control with IKFK_Seamless attribute.\n\nSwitch Control: {}\nSwitch Attribute: IKFK_Seamless\n\nPlease verify and load remaining controls manually if needed.'.format(ctrl),
+ button=['Build Now', 'Cancel'],
+ defaultButton='Build Now',
+ cancelButton='Cancel',
+ dismissString='Cancel'
+ )
+
+ if result == 'Build Now':
+ self.build_seamless_switching()
+ else:
+ # 没有找到IKFK_Seamless属性
+ cmds.confirmDialog(
+ title='Not AdvanceSkeleton',
+ message='Selected control does not have IKFK_Seamless attribute.\n\nThis ADV Build feature is designed for AdvanceSkeleton rigs.\n\nPlease select an AdvanceSkeleton FK or IK control.',
+ button=['OK'],
+ defaultButton='OK'
+ )
+
+ def _auto_detect_adv_skeleton_controls(self, ctrl):
+ """自动检测AdvanceSkeleton控制器"""
+ # 获取命名空间和控制器名称
+ namespace = ''
+ ctrl_name = ctrl
+ if ':' in ctrl:
+ parts = ctrl.split(':')
+ namespace = ':'.join(parts[:-1]) + ':'
+ ctrl_name = parts[-1]
+
+ print("Auto-detecting AdvanceSkeleton controls for: {}".format(ctrl_name))
+
+ # 确定是左侧还是右侧
+ side = ''
+ if ctrl_name.endswith('_L'):
+ side = '_L'
+ elif ctrl_name.endswith('_R'):
+ side = '_R'
+
+ # 检测手臂控制器
+ if 'Arm' in ctrl_name:
+ self._load_adv_arm_controls(namespace, side)
+ # 检测腿部控制器
+ elif 'Leg' in ctrl_name:
+ self._load_adv_leg_controls(namespace, side)
+ else:
+ print("Unknown limb type, please load controls manually")
+
+ def _load_adv_arm_controls(self, namespace, side):
+ """加载AdvanceSkeleton手臂控制器"""
+ print("Loading AdvanceSkeleton Arm controls for side: {}".format(side))
+
+ # AdvanceSkeleton手臂命名规范
+ # 关节
+ shoulder_jnt = "{}FKShoulder{}".format(namespace, side)
+ elbow_jnt = "{}FKElbow{}".format(namespace, side)
+ wrist_jnt = "{}FKWrist{}".format(namespace, side)
+
+ # FK控制器
+ fk_shoulder = "{}FKShoulder{}".format(namespace, side)
+ fk_elbow = "{}FKElbow{}".format(namespace, side)
+ fk_wrist = "{}FKWrist{}".format(namespace, side)
+
+ # IK控制器
+ ik_wrist = "{}IKArm{}".format(namespace, side)
+ ik_pole = "{}PoleArm{}".format(namespace, side)
+
+ # 尝试加载关节
+ self._try_load_control(shoulder_jnt, 'joint', 'shoulder')
+ self._try_load_control(elbow_jnt, 'joint', 'elbow')
+ self._try_load_control(wrist_jnt, 'joint', 'wrist')
+
+ # 尝试加载FK控制器
+ self._try_load_control(fk_shoulder, 'fk', 'shoulder')
+ self._try_load_control(fk_elbow, 'fk', 'elbow')
+ self._try_load_control(fk_wrist, 'fk', 'wrist')
+
+ # 尝试加载IK控制器
+ self._try_load_control(ik_wrist, 'ik', 'wrist')
+ self._try_load_control(ik_pole, 'ik', 'pole')
+
+ def _load_adv_leg_controls(self, namespace, side):
+ """加载AdvanceSkeleton腿部控制器"""
+ print("Loading AdvanceSkeleton Leg controls for side: {}".format(side))
+
+ # AdvanceSkeleton腿部命名规范
+ # 关节
+ hip_jnt = "{}FKHip{}".format(namespace, side)
+ knee_jnt = "{}FKKnee{}".format(namespace, side)
+ ankle_jnt = "{}FKAnkle{}".format(namespace, side)
+
+ # FK控制器
+ fk_hip = "{}FKHip{}".format(namespace, side)
+ fk_knee = "{}FKKnee{}".format(namespace, side)
+ fk_ankle = "{}FKAnkle{}".format(namespace, side)
+
+ # IK控制器
+ ik_ankle = "{}IKLeg{}".format(namespace, side)
+ ik_pole = "{}PoleLeg{}".format(namespace, side)
+
+ # 尝试加载关节
+ self._try_load_control(hip_jnt, 'joint', 'shoulder') # 使用shoulder位置存储hip
+ self._try_load_control(knee_jnt, 'joint', 'elbow') # 使用elbow位置存储knee
+ self._try_load_control(ankle_jnt, 'joint', 'wrist') # 使用wrist位置存储ankle
+
+ # 尝试加载FK控制器
+ self._try_load_control(fk_hip, 'fk', 'shoulder')
+ self._try_load_control(fk_knee, 'fk', 'elbow')
+ self._try_load_control(fk_ankle, 'fk', 'wrist')
+
+ # 尝试加载IK控制器
+ self._try_load_control(ik_ankle, 'ik', 'wrist')
+ self._try_load_control(ik_pole, 'ik', 'pole')
+
+ def _try_load_control(self, ctrl_name, ctrl_type, position):
+ """尝试加载控制器到对应位置"""
+ if cmds.objExists(ctrl_name):
+ if ctrl_type == 'joint':
+ self.joints[position] = ctrl_name
+ field_name = 'joint_{}_field'.format(position)
+ if field_name in self.ui_elements:
+ cmds.textField(self.ui_elements[field_name], edit=True, text=ctrl_name)
+ print(" Loaded Joint {}: {}".format(position, ctrl_name))
+
+ elif ctrl_type == 'fk':
+ self.fk_ctrls[position] = ctrl_name
+ field_name = 'fk_{}_field'.format(position)
+ if field_name in self.ui_elements:
+ cmds.textField(self.ui_elements[field_name], edit=True, text=ctrl_name)
+ print(" Loaded FK {}: {}".format(position, ctrl_name))
+
+ elif ctrl_type == 'ik':
+ self.ik_ctrls[position] = ctrl_name
+ field_name = 'ik_{}_field'.format(position)
+ if field_name in self.ui_elements:
+ cmds.textField(self.ui_elements[field_name], edit=True, text=ctrl_name)
+ print(" Loaded IK {}: {}".format(position, ctrl_name))
+ else:
+ print(" Warning: Control not found: {}".format(ctrl_name))
+
+ def empty_fields(self, *args):
+ """清空所有字段"""
+ # 清空关节字段
+ for key in self.joints.keys():
+ self.joints[key] = None
+
+ # 清空FK控制器字段
+ for key in self.fk_ctrls.keys():
+ self.fk_ctrls[key] = None
+
+ # 清空IK控制器字段
+ for key in self.ik_ctrls.keys():
+ self.ik_ctrls[key] = None
+
+ # 清空切换控制器
+ self.switch_ctrl = None
+ self.switch_attr = None
+
+ # 清空UI文本框
+ for field_name, field_widget in self.ui_elements.items():
+ if cmds.textField(field_widget, exists=True):
+ cmds.textField(field_widget, edit=True, text='')
+
+ # 清除scriptJob
+ self.clear_script_jobs()
+
+ print("All fields cleared")
+ cmds.inViewMessage(
+ amg='All fields cleared!',
+ pos='midCenter',
+ fade=True,
+ fadeStayTime=1000,
+ fadeOutTime=500
+ )
+
+ def show_help(self, *args):
+ """显示帮助信息"""
+ help_text = """
+IKFK Seamless Switching Tool - Help
+
+使用步骤:
+1. 加载关节 (Load Joint)
+ - 依次选择并加载肩部、肘部、腕部关节
+
+2. 加载FK控制器 (Load FK Ctrl)
+ - 依次选择并加载FK肩部、肘部、腕部控制器
+
+3. 加载IK控制器 (Load IK Ctrl)
+ - 选择并加载IK腕部控制器和极向量控制器
+
+4. 加载切换控制器 (Load Switch Ctrl)
+ - 选择切换控制器
+ - 在通道盒中选择切换属性后点击加载
+
+5. 构建无缝切换
+ - 点击 "Build Seamless Switching" 按钮
+ - 系统将创建scriptJob监听切换属性
+ - 当切换属性改变时自动匹配IK/FK位置
+
+注意:
+- scriptJob会在场景关闭时自动清除
+- 切换属性值: 0=FK, 1=IK
+ """
+
+ result = cmds.confirmDialog(
+ title='Help',
+ message=help_text,
+ button=['OK'],
+ defaultButton='OK'
+ )
+
+ def close_window(self):
+ """关闭窗口并清理"""
+ self.clear_script_jobs()
+ if cmds.window(self.window_name, exists=True):
+ cmds.deleteUI(self.window_name)
+
+
+# 全局实例
+_ikfk_switcher_instance = None
+
+
+def show():
+ """显示IKFK切换工具窗口"""
+ global _ikfk_switcher_instance
+
+ # 如果实例存在,先清理
+ if _ikfk_switcher_instance:
+ _ikfk_switcher_instance.close_window()
+
+ # 创建新实例
+ _ikfk_switcher_instance = IKFKSwitcher()
+ _ikfk_switcher_instance.create_ui()
+
+ return _ikfk_switcher_instance
+
+
+def start():
+ """启动函数(兼容性别名)"""
+ return show()
+
+
+if __name__ == "__main__":
+ show()
diff --git a/2023/shelves/shelf_Nexus_Animation.mel b/2023/shelves/shelf_Nexus_Animation.mel
index 981e21f..c74902e 100644
--- a/2023/shelves/shelf_Nexus_Animation.mel
+++ b/2023/shelves/shelf_Nexus_Animation.mel
@@ -74,6 +74,41 @@ global proc shelf_Nexus_Animation () {
-commandRepeatable 1
-flat 1
;
+ shelfButton
+ -enableCommandRepeat 1
+ -flexibleWidthType 3
+ -flexibleWidthValue 32
+ -enable 1
+ -width 35
+ -height 34
+ -manage 1
+ -visible 1
+ -preventOverride 0
+ -annotation "IKFK Seamless Switching - Real-time IK/FK switching with scriptJob"
+ -enableBackground 0
+ -backgroundColor 0 0 0
+ -highlightColor 0.321569 0.521569 0.65098
+ -align "center"
+ -label "IKFK"
+ -labelOffset 0
+ -rotation 0
+ -flipX 0
+ -flipY 0
+ -useAlpha 1
+ -font "plainLabelFont"
+ -imageOverlayLabel "IKFK"
+ -overlayLabelColor 0.8 0.8 0.8
+ -overlayLabelBackColor 0 0 0 0.5
+ -image "ikfk_switcher.png"
+ -image1 "ikfk_switcher.png"
+ -style "iconOnly"
+ -marginWidth 0
+ -marginHeight 1
+ -command "import animation_tools.ikfk_switcher\nanimation_tools.ikfk_switcher.show()"
+ -sourceType "python"
+ -commandRepeatable 1
+ -flat 1
+ ;
shelfButton
-enableCommandRepeat 1
-flexibleWidthType 3