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