This commit is contained in:
2025-11-24 22:59:21 +08:00
parent 719058ff01
commit 6d2a21c30b
2 changed files with 40 additions and 83 deletions

View File

@@ -14,7 +14,7 @@ import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMaya as om
#from math import isclose
from sys import exit
# from sys import exit # 已移除,避免 SystemExit 错误
##################################################################################################################################################################################################################
@@ -72,7 +72,7 @@ def assist_message(message, time, to_exit=True): # ---------- DONE
#POPS UP A MESSAGE ON THE USER'S SCREEN TO INFORM THEM OF SOMETHING
cmds.inViewMessage(amg="<hl>" + message + "<hl>", pos='midCenter', fade=True, fst=time, ck=True)
if to_exit:
exit()
return # 不使用 exit(),避免 SystemExit 错误
def get_constraint_attribute(constraint_type):
#SETS A KEY ON THE START AND END OF THE TIMELINE, SO THAT WE ENSURE THERE'S A BLEND NODE ALL THE TIME. IF THERE'S NO KEY BEFORE ADDING THE SETUP, THE SCRIPT WON'T APPLY A SWITCH ON THE BLEND NODE
@@ -409,6 +409,14 @@ def set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls):
#CREATES A TEMPORARY IK SET-UP BY SELECTING EXISTING FK CONTROLS
def fk_to_ik():
try:
_fk_to_ik_impl()
except Exception as e:
if 'SystemExit' not in str(type(e)):
import maya.cmds as cmds
cmds.warning('FK to IK failed: ' + str(e))
def _fk_to_ik_impl():
global timeline_start, timeline_end, specific_timeline_mode, influence_visibility
influence_visibility = cmds.checkBoxGrp("influence_visibility", q=True, v1=True)
clear_keys = cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)
@@ -569,6 +577,14 @@ def reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jn
#CREATES A TEMPORARY FK SET-UP BY SELECTING EXISTING IK CONTROLS
def ik_to_fk():
try:
_ik_to_fk_impl()
except Exception as e:
if 'SystemExit' not in str(type(e)):
import maya.cmds as cmds
cmds.warning('IK to FK failed: ' + str(e))
def _ik_to_fk_impl():
global timeline_start, timeline_end, specific_timeline_mode, influence_visibility
influence_visibility = cmds.checkBoxGrp("influence_visibility", q=True, v1=True)
clear_keys = cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)
@@ -703,9 +719,19 @@ def delete_ik_blend(i, group_Contents):
def delete_setup():
try:
_delete_setup_impl()
except Exception as e:
# 捕获所有错误,避免 SystemExit
if "SystemExit" not in str(type(e)):
cmds.warning("Delete setup failed: " + str(e)) # ✓ 使用字符串拼接
def _delete_setup_impl():
clear_keys = cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)
#BAKES THE PREVIOUS CONTROLS, CLEANS UP THE CURVES, DELETES CURRENT CONTROLS AND BRINGS BACK ORIGINALS
temp_selection = get_temporary_setup_selection()
if not temp_selection:
return
for selection in temp_selection:
if cmds.objExists(selection):
check_if_correct_selection(selection)
@@ -742,20 +768,19 @@ def user_interface():
if cmds.window("IK_FK_Switcher", ex=True):
cmds.deleteUI("IK_FK_Switcher")
cmds.window("IK_FK_Switcher", title="IK/FK Switcher, by Petar3D", wh=[360, 242], s=False)
cmds.formLayout("ikfk_form_layout", numberOfDivisions=100, w=360, h=242)
cmds.button("fkToIK_Button", l="FK to IK", recomputeSize = True, bgc=[0.6220035095750363, 0.8836957351033798, 1.0], h = 43, w = 100, parent ="ikfk_form_layout", command="fk_to_ik()",
cmds.button("fkToIK_Button", l="FK to IK", recomputeSize = True, bgc=[0.6220035095750363, 0.8836957351033798, 1.0], h = 43, w = 100, parent ="ikfk_form_layout", command=lambda *args: fk_to_ik(),
ann="Applies a temporary IK setup on top of your existing FK chain.\nHow to use: Select 3 FK controls, starting from the parent to the child, then click this button.")
set_form_layout_coordinates("ikfk_form_layout", "fkToIK_Button", 16, 16)
cmds.button("ikToFK_Button", l="IK to FK ", recomputeSize = True, bgc=[1.0, 1.0, 0.6220035095750363], h = 43, w = 100, parent ="ikfk_form_layout", command="ik_to_fk()",
cmds.button("ikToFK_Button", l="IK to FK ", recomputeSize = True, bgc=[1.0, 1.0, 0.6220035095750363], h = 43, w = 100, parent ="ikfk_form_layout", command=lambda *args: ik_to_fk(),
ann="Applies a temporary FK setup on top of your existing IK chain.\nHow to use: Select the pole vector and then the IK control, then click this button.")
set_form_layout_coordinates("ikfk_form_layout", "ikToFK_Button", 16, 131)
cmds.button("DeleteSetup_Button", l="Delete Setup", recomputeSize = True, bgc=[1.0, 0.6220035095750363, 0.6220035095750363], h = 43, w = 99, parent ="ikfk_form_layout", command="delete_setup()",
cmds.button("DeleteSetup_Button", l="Delete Setup", recomputeSize = True, bgc=[1.0, 0.6220035095750363, 0.6220035095750363], h = 43, w = 99, parent ="ikfk_form_layout", command=lambda *args: delete_setup(),
ann="Deletes the temporary IK/FK setups and brings back the original.\nHow to use: Select a control from the current setup, then click this button.")
set_form_layout_coordinates("ikfk_form_layout", "DeleteSetup_Button", 16, 246)
@@ -788,12 +813,12 @@ def user_interface():
cmds.button("ExtraOptions_Button", l="Extra Options", recomputeSize=True,
bgc=[0.6220035095750363, 1.0, 0.6656137941557946], h=34, w=90, parent="ikfk_form_layout",
command="extraOptions()")
command=lambda *args: extraOptions())
set_form_layout_coordinates("ikfk_form_layout", "ExtraOptions_Button", 194, 255)
cmds.button("ik_fk_documentation", l="Documentation", recomputeSize=True,
bgc=[0.8, 0.8, 0.8], h=34, w=100, parent="ikfk_form_layout",
command="documentation()")
command=lambda *args: documentation())
set_form_layout_coordinates("ikfk_form_layout", "ik_fk_documentation", 194, 16.5)
# cmds.separator("Proxy_HRSeparator", hr=True,bgc=[0.6569924467841611, 0.6569924467841611, 0.6569924467841611], style="none", h = 3, w = 394)
@@ -812,7 +837,7 @@ import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMaya as om
from sys import exit
# from sys import exit # 已移除,避免 SystemExit 错误
bake_interval = """ + str(cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)) + """
apply_key_reducer = """ + str(cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True)) + """
@@ -873,8 +898,9 @@ def set_form_layout_coordinates(form_layout, name, top_coordinates, left_coordin
def assist_message(message, time, to_exit=True): # ---------- DONE
#POPS UP A MESSAGE ON THE USER'S SCREEN TO INFORM THEM OF SOMETHING
cmds.inViewMessage(amg="<hl>" + message + "<hl>", pos='midCenter', fade=True, fst=time, ck=True)
if to_exit:
exit()
# 不再使用 exit(),因为它会在 Maya 中产生 SystemExit 错误
# 调用者应该检查返回值并决定是否继续执行
return not to_exit # 返回 False 表示应该停止True 表示可以继续
def get_constraint_attribute(constraint_type):
#SETS A KEY ON THE START AND END OF THE TIMELINE, SO THAT WE ENSURE THERE'S A BLEND NODE ALL THE TIME. IF THERE'S NO KEY BEFORE ADDING THE SETUP, THE SCRIPT WON'T APPLY A SWITCH ON THE BLEND NODE
@@ -923,6 +949,8 @@ def check_negative_time_range(timelineStart, timelineEnd):
# PREVENTS THE USER FROM APPLYING THE SETUP IN A NEGATIVE RANGE TIMELINE
if timelineStart < 0 or timelineEnd < 0:
assist_message("Error: You can't apply a locator setup on a negative time-range", 5000, True)
return False
return True
def get_timeline_range():
aTimeSlider = mel.eval('$tmpVar=$gPlayBackSlider')
@@ -1602,7 +1630,7 @@ def extraOptions():
cmds.button("Generate_Code_Button", l="Generate Code", recomputeSize=True,
bgc=[0.6220035095750363, 1.0, 0.7237659266041047], h=44, w=110, parent="ikfk_form_layout_extra",
command="generateCode()",
command=lambda *args: generateCode(),
ann="It isolates the code from the UI, so you can put it on a shelf or add it to a marking menu, and you don't have to come back to the UI every time.\nThere's 2 ways to use this.\n"
"Specific setup: If you select the controls in the scene as if you were applying the setup, when you hit generate it'll store the selection into the code, so you don't have to select them every time.\n"
"General setup: If you have nothing selected in the scene and hit generate, it'll produce a generic version of the code, and you'll have to select the controls every time before executing the code, but it's more flexible.\n"

View File

@@ -1,71 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
IK/FK Switcher 测试脚本
在 Maya 中运行此脚本来测试模块是否正常工作
"""
def test_ik_fk_switcher():
"""测试 ik_fk_switcher 模块的导入和函数"""
print("=" * 60)
print("IK/FK Switcher 测试")
print("=" * 60)
try:
# 测试导入
print("\n[1/5] 测试导入模块...")
import animation_tools.ik_fk_switcher as ikfk
print("✓ 成功导入 ik_fk_switcher")
# 测试 __all__ 导出
print("\n[2/5] 检查 __all__ 导出列表...")
if hasattr(ikfk, '__all__'):
print(f"✓ __all__ = {ikfk.__all__}")
else:
print("✗ 没有找到 __all__")
# 测试主要函数
print("\n[3/5] 检查主要函数...")
functions = ['fk_to_ik', 'ik_to_fk', 'delete_setup', 'documentation', 'extraOptions', 'user_interface', 'show']
for func_name in functions:
if hasattr(ikfk, func_name):
func = getattr(ikfk, func_name)
print(f"{func_name}: {type(func)}")
else:
print(f"{func_name}: 未找到")
# 测试 show() 函数
print("\n[4/5] 测试 show() 函数...")
if hasattr(ikfk, 'show') and callable(ikfk.show):
print("✓ show() 函数可调用")
print(f" 文档: {ikfk.show.__doc__}")
else:
print("✗ show() 函数不可用")
# 测试 dir()
print("\n[5/5] 列出所有可用属性...")
all_attrs = [attr for attr in dir(ikfk) if not attr.startswith('_')]
print(f"✓ 共有 {len(all_attrs)} 个公共属性/函数")
print(f" 主要函数: {[attr for attr in all_attrs if callable(getattr(ikfk, attr))][:10]}")
print("\n" + "=" * 60)
print("所有测试通过!")
print("=" * 60)
print("\n使用方法:")
print(" import animation_tools.ik_fk_switcher as ikfk")
print(" ikfk.show() # 启动 UI")
print("=" * 60)
return True
except Exception as e:
print(f"\n✗ 测试失败: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
# 在 Maya 中运行
test_ik_fk_switcher()