From 021c593241fd5f5ad6aff06d31820e01dfaf5955 Mon Sep 17 00:00:00 2001 From: jeffreytsai1004 Date: Wed, 26 Nov 2025 00:15:31 +0800 Subject: [PATCH] Update --- 2023/icons/ikfk_switcher.png | Bin 0 -> 6336 bytes 2023/scripts/animation_tools/ikfk_switcher.py | 706 ++++++++++++++++++ 2023/shelves/shelf_Nexus_Animation.mel | 35 + 3 files changed, 741 insertions(+) create mode 100644 2023/icons/ikfk_switcher.png create mode 100644 2023/scripts/animation_tools/ikfk_switcher.py diff --git a/2023/icons/ikfk_switcher.png b/2023/icons/ikfk_switcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b8211f0db7ac69967802701bec73f6554ca86fa5 GIT binary patch literal 6336 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 DlIQe_ literal 0 HcmV?d00001 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