Update
This commit is contained in:
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
150
2023/scripts/animation_tools/IKFK_SWITCH_README.md
Normal file
150
2023/scripts/animation_tools/IKFK_SWITCH_README.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# IKFK Switch Tool
|
||||
|
||||
## 概述
|
||||
IKFK Switch Tool 是一个用于在Maya中实现IK和FK动画之间无缝切换的工具。该工具从原始MEL脚本转换为Python版本,提供更好的跨版本兼容性。
|
||||
|
||||
## 版本信息
|
||||
- **版本**: V1.0
|
||||
- **兼容性**: Maya 2018+
|
||||
- **语言**: Python
|
||||
- **原始版本**: MEL (ikfk_switch.mel)
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 主要功能
|
||||
1. **无缝切换**: 在IK和FK动画之间进行无缝切换,保持动画连续性
|
||||
2. **自动匹配**: 切换时自动匹配控制器位置和旋转
|
||||
3. **关键帧设置**: 自动在切换点设置关键帧
|
||||
4. **预设支持**: 内置ADV绑定预设,支持快速设置
|
||||
|
||||
### UI功能
|
||||
- **FK控制器加载**: 加载FK骨骼和控制器
|
||||
- FK Joint Root/Mid/End
|
||||
- FK Ctrl Root/Mid/End
|
||||
|
||||
- **IK控制器加载**: 加载IK骨骼和控制器
|
||||
- IK Joint Root/Mid/End
|
||||
- IK Ctrl Root (IK手柄)
|
||||
- IK Ctrl Pole (极向量控制器)
|
||||
|
||||
- **切换控制器**: 设置切换属性
|
||||
- Switch Ctrl (切换控制器)
|
||||
- Switch Attr (切换属性名称)
|
||||
|
||||
### 辅助功能
|
||||
- **ADV Build**: 快速为ADV绑定设置IKFK切换
|
||||
- **Empty**: 清空所有输入字段
|
||||
- **BiliBili**: 打开教程链接
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本使用流程
|
||||
|
||||
1. **加载FK控制器**
|
||||
- 选择FK骨骼根节点,点击"< FK Joint Root"
|
||||
- 选择FK骨骼中间节点,点击"< FK Joint Mid"
|
||||
- 选择FK骨骼末端节点,点击"< FK Joint End"
|
||||
- 重复以上步骤加载FK控制器
|
||||
|
||||
2. **加载IK控制器**
|
||||
- 选择IK骨骼节点,点击相应按钮
|
||||
- 选择IK控制器和极向量控制器
|
||||
|
||||
3. **设置切换控制器**
|
||||
- 选择用于切换的控制器,点击"< Switch Ctrl"
|
||||
- 在通道盒中选择切换属性,点击"< Switch Attr"
|
||||
|
||||
4. **构建切换系统**
|
||||
- 点击"<<< Build Seamless Switching >>>"按钮
|
||||
- 系统会自动创建必要的属性和脚本节点
|
||||
|
||||
5. **使用切换功能**
|
||||
- 在切换控制器上找到"IKFK_Seamless"属性
|
||||
- 切换该属性值(IK=0, FK=1)即可实现无缝切换
|
||||
|
||||
### ADV绑定快速设置
|
||||
|
||||
如果使用ADV绑定系统,可以使用预设功能:
|
||||
1. 展开"Edit"面板
|
||||
2. 点击"<<< ADV Build >>>"
|
||||
3. 系统会自动为左右手臂和腿部设置IKFK切换
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心组件
|
||||
|
||||
1. **IKFKSwitchUI类**: UI界面管理
|
||||
- 创建和管理UI窗口
|
||||
- 处理用户输入
|
||||
- 调用切换逻辑
|
||||
|
||||
2. **seamless_switching函数**: 核心切换逻辑
|
||||
- 创建数据存储locator
|
||||
- 添加必要属性
|
||||
- 建立属性连接
|
||||
- 创建脚本节点
|
||||
|
||||
3. **create_switching_script函数**: 创建全局切换脚本
|
||||
- 使用MEL脚本实现切换逻辑
|
||||
- 处理约束和关键帧
|
||||
|
||||
4. **create_script_job函数**: 创建脚本任务
|
||||
- 监听属性变化
|
||||
- 自动触发切换
|
||||
|
||||
### 兼容性设计
|
||||
|
||||
- 使用`maya.cmds`和`maya.mel`模块,确保跨版本兼容
|
||||
- 避免使用版本特定的API
|
||||
- 使用f-string格式化(Python 3.6+)
|
||||
- 异常处理确保稳定性
|
||||
|
||||
## 工具架集成
|
||||
|
||||
该工具已集成到Nexus Animation工具架:
|
||||
- **图标**: ikfk_switch.png
|
||||
- **位置**: 动画工具架末尾
|
||||
- **调用**: `import animation_tools.ikfx_switch; animation_tools.ikfx_switch.show()`
|
||||
|
||||
## 文件位置
|
||||
|
||||
### Maya 2023
|
||||
- 脚本: `scripts/animation_tools/ikfx_switch.py`
|
||||
- 图标: `icons/ikfk_switch.png`
|
||||
- 工具架: `shelves/shelf_Nexus_Animation.mel`
|
||||
|
||||
### Maya 2025
|
||||
- 脚本: `scripts/animation_tools/ikfx_switch.py`
|
||||
- 图标: `icons/ikfk_switch.png`
|
||||
- 工具架: `shelves/shelf_Nexus_Animation.mel`
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **属性要求**: 切换属性必须有最小值和最大值设置
|
||||
2. **命名规范**: 建议使用清晰的命名规范以便识别
|
||||
3. **关键帧**: 切换会在当前帧和前一帧设置关键帧
|
||||
4. **选择保持**: 切换后会恢复原始选择
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **切换不工作**
|
||||
- 检查所有必需的对象是否存在
|
||||
- 确认切换属性已正确设置
|
||||
- 查看脚本编辑器的错误信息
|
||||
|
||||
2. **位置不匹配**
|
||||
- 确保FK和IK骨骼层级正确
|
||||
- 检查约束设置
|
||||
- 验证控制器方向
|
||||
|
||||
3. **属性加载失败**
|
||||
- 确保在通道盒中选择了属性
|
||||
- 检查属性是否可关键帧化
|
||||
|
||||
## 开发者信息
|
||||
|
||||
- 转换: Python版本转换自MEL脚本
|
||||
- 维护: Nexus工具集
|
||||
- 更新日期: 2024
|
||||
@@ -1,301 +0,0 @@
|
||||
from maya import cmds
|
||||
from maya.api import OpenMaya as om2
|
||||
#import logging
|
||||
'''
|
||||
Create in ADV 6.040, select the corresponding IKFK controller, and simply run the script to switch
|
||||
by kangddan
|
||||
'''
|
||||
#logger = logging.getLogger(__name__)
|
||||
|
||||
def setAttrs(nodes, attrs, value):
|
||||
|
||||
for n in nodes:
|
||||
for ids, i in enumerate(attrs):
|
||||
try:
|
||||
if isinstance(value[ids], (list, tuple)):
|
||||
cmds.setAttr('{}.{}'.format(n, i), *value[ids])
|
||||
else:
|
||||
cmds.setAttr('{}.{}'.format(n, i), value[ids])
|
||||
except:
|
||||
pass
|
||||
|
||||
class Path:
|
||||
@classmethod
|
||||
def path(cls, fullPathName):
|
||||
return fullPathName.split("|")[-1]
|
||||
|
||||
@classmethod
|
||||
def name(cls, fullPathName):
|
||||
return cls.path(fullPathName).split(':')[-1]
|
||||
|
||||
@classmethod
|
||||
def namespace(cls, fullPathName):
|
||||
return cls.path(fullPathName).rpartition(':')[0]
|
||||
|
||||
class MOpenMaya:
|
||||
|
||||
@staticmethod
|
||||
def toMObject(nodeName):
|
||||
sl = om2.MSelectionList(); sl.add(nodeName)
|
||||
return sl.getDependNode(0)
|
||||
|
||||
@classmethod
|
||||
def getDagNode(cls, mobj) :
|
||||
if not mobj.hasFn(om2.MFn.kDagNode): return
|
||||
return om2.MDagPath.getAPathTo(mobj)
|
||||
|
||||
@classmethod
|
||||
def getGlobatMatrix(cls, dagNode):
|
||||
if not isinstance(dagNode, om2.MDagPath): return
|
||||
return dagNode.inclusiveMatrix()
|
||||
|
||||
|
||||
@classmethod
|
||||
def getGlobalPos(cls, dagNode):
|
||||
if not dagNode: return
|
||||
tm = om2.MTransformationMatrix(cls.getGlobatMatrix(dagNode))
|
||||
return tm.translation(om2.MSpace.kTransform)
|
||||
|
||||
@classmethod
|
||||
def ls(cls):
|
||||
sl = om2.MGlobal.getActiveSelectionList()
|
||||
return [cls.getDagNode(sl.getDependNode(o)) for o in range(sl.length())]
|
||||
|
||||
|
||||
class IkFk:
|
||||
ARM_IK_CTRLS = {'ik1':'IKArm', 'ik2':'PoleArm'} # 0
|
||||
ARM_FK_CTRLS = {'fk1':'FKShoulder', 'fk2':'FKElbow', 'fk3':'FKWrist'} # 1
|
||||
ARM_FKTOIK_LOCS = {'loc1':'IKX2Shoulder', 'loc2':'IKX2Elbow', 'loc3':'IKX2Wrist'} # 2
|
||||
ARM_IKTOFK_LOCS = {'loc1':'AlignIKToWrist'} # 3
|
||||
ARM_IKFK = {'switch':'FKIKArm'}
|
||||
|
||||
LEG_IK_CTRLS = {'ik1':'IKLeg', 'ik2':'PoleLeg', 'ik3':'RollHeel', 'ik4':'RollToesEnd', 'ik5':'RollToes', 'ik6':'IKToes'}
|
||||
LEG_FK_CTRLS = {'fk1':'FKHip', 'fk2':'FKKnee', 'fk3':'FKAnkle', 'fk4':'FKToes'}
|
||||
LEG_FKTOIK_LOCS = {'loc1':'IKX2Hip', 'loc2':'IKX2Knee', 'loc3':'IKX2Ankle', 'loc4':'IKX2Toes', 'loc5':'IKX2ToesEnd'}
|
||||
LEG_IKTOFK_LOCS = {'loc1':'AlignIKToAnkle', 'loc2':'AlignIKToToes'}
|
||||
leg_IKFK = {'switch':'FKIKLeg'}
|
||||
|
||||
# --------------------------------------------------------------
|
||||
ARM_BASE_JNTS = {'base1':'Shoulder', 'base2':'Elbow', 'base3':'Wrist', 'root':'RootSystem'}
|
||||
LEG_BASE_JNTS = {'base1':'Hip', 'base2':'Knee', 'base3':'Ankle', 'root':'RootSystem'}
|
||||
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def getLRDict(_dict, namespace=''):
|
||||
|
||||
ldictList = [{k:'{}{}_L'.format(namespace, v) for k, v in d.items()} for d in _dict[:-2]]
|
||||
rdictList = [{k:'{}{}_R'.format(namespace, v) for k, v in d.items()} for d in _dict[:-2]]
|
||||
basedictList = [{k:'{}{}'.format(namespace, v) for k, v in d.items()} for d in _dict[-2:]]
|
||||
|
||||
return ldictList, rdictList, basedictList
|
||||
|
||||
@staticmethod
|
||||
def getLongName(sName, _dict):
|
||||
for k, v in _dict.items():
|
||||
longNames = cmds.ls(v, long=True)
|
||||
for ln in longNames:
|
||||
if sName == ln[0:len(sName)]:
|
||||
_dict[k] = ln
|
||||
# if ln[0].startswith(sName):
|
||||
# _dict[k] = ln
|
||||
break
|
||||
|
||||
# --------------------------------------------------------------
|
||||
|
||||
def newDict(self, fullPathName):
|
||||
dictList = [IkFk.ARM_IK_CTRLS, IkFk.ARM_FK_CTRLS, IkFk.ARM_FKTOIK_LOCS, IkFk.ARM_IKTOFK_LOCS, IkFk.ARM_IKFK,
|
||||
IkFk.LEG_IK_CTRLS, IkFk.LEG_FK_CTRLS, IkFk.LEG_FKTOIK_LOCS, IkFk.LEG_IKTOFK_LOCS, IkFk.leg_IKFK,
|
||||
IkFk.ARM_BASE_JNTS, IkFk.LEG_BASE_JNTS]
|
||||
|
||||
namespace = Path.namespace(fullPathName)
|
||||
if namespace:
|
||||
self.isRef = True
|
||||
return self.getLRDict(dictList, namespace+':')
|
||||
else:
|
||||
self.isRef = False
|
||||
sName = '|' + cmds.ls('*{}'.format(fullPathName), long=True)[0].split('|')[1]
|
||||
ldictList, rdictList, basedictList = self.getLRDict(dictList)
|
||||
|
||||
for dL, dR in zip(ldictList, rdictList):
|
||||
self.getLongName(sName, dL)
|
||||
self.getLongName(sName, dR)
|
||||
|
||||
for bS in basedictList: self.getLongName(sName, bS) #
|
||||
|
||||
return ldictList, rdictList, basedictList
|
||||
|
||||
@staticmethod
|
||||
def setPolePose(jntList, pole):
|
||||
jntPos = [MOpenMaya.getGlobalPos(MOpenMaya.getDagNode(MOpenMaya.toMObject(j))) for j in jntList]
|
||||
off = ((jntPos[1] - jntPos[0]).length() + (jntPos[2] - jntPos[1]).length())
|
||||
|
||||
midPos = ((jntPos[1] - jntPos[0]).normal() + ((jntPos[2] - jntPos[1]).normal() * -1)) * 0.5
|
||||
polePos = (midPos.normal() * off) + jntPos[1]
|
||||
cmds.xform(pole, t=polePos, ws=True)
|
||||
|
||||
@staticmethod
|
||||
def setIkStretch(jntList, locList):
|
||||
jntPos = [MOpenMaya.getGlobalPos(MOpenMaya.getDagNode(MOpenMaya.toMObject(j))) for j in jntList]
|
||||
locPos = [MOpenMaya.getGlobalPos(MOpenMaya.getDagNode(MOpenMaya.toMObject(l))) for l in locList]
|
||||
|
||||
upper = (jntPos[1] - jntPos[0]).length()
|
||||
mid = (jntPos[2] - jntPos[1]).length()
|
||||
|
||||
locUpper = (locPos[1] - locPos[0]).length()
|
||||
locMid = (locPos[2] - locPos[1]).length()
|
||||
|
||||
upperError = abs(upper / locUpper)
|
||||
midErrorr = abs(mid / locMid)
|
||||
return upperError, midErrorr
|
||||
|
||||
def getNewDict(self, fullPathName):
|
||||
ldictList, rdictList, basedictList = self.newDict(fullPathName)
|
||||
ALD = ldictList[0:5]; LLD = ldictList[5:]
|
||||
ARD = rdictList[0:5]; LRD = rdictList[5:]
|
||||
|
||||
AB = basedictList[0]; LB = basedictList[1] # base joint ref
|
||||
|
||||
return ALD, ARD, LLD, LRD, AB, LB
|
||||
|
||||
@staticmethod
|
||||
def _updateDict(_dict):
|
||||
return [value for d in [_dict[0], _dict[1], _dict[-1]] for value in d.values()]
|
||||
|
||||
def __init__(self, autoK=True):
|
||||
self.curTime , self.prevTime = cmds.currentTime(q=True), cmds.currentTime(q=True)-1
|
||||
self.autoK = autoK
|
||||
self.isRef = False
|
||||
self.ikfkSwitch()
|
||||
|
||||
|
||||
def autoKey(self, nodes=[], num=None):
|
||||
if not self.autoK:
|
||||
return
|
||||
for i in nodes:
|
||||
try:
|
||||
if cmds.keyframe(i, q=True, kc=True):
|
||||
cmds.setKeyframe(i, i=True, t=num)
|
||||
else:
|
||||
cmds.setKeyframe(i, i=False, t=num)
|
||||
except:
|
||||
continue
|
||||
|
||||
def ikfkSwitch(self):
|
||||
|
||||
sl = MOpenMaya.ls()
|
||||
for i in sl:
|
||||
if i is None:
|
||||
om2.MGlobal.displayWarning('Please select a DAG node')
|
||||
continue
|
||||
|
||||
ALD, ARD, LLD, LRD, AB, LB = self.getNewDict(i.fullPathName())
|
||||
path = Path.path(i.fullPathName()) if self.isRef else i.fullPathName()
|
||||
|
||||
if path in self._updateDict(ALD): # L arm
|
||||
self.armIKFk(ALD, AB)
|
||||
|
||||
elif path in self._updateDict(ARD): # R arm
|
||||
self.armIKFk(ARD, AB)
|
||||
|
||||
elif path in self._updateDict(LLD): # L leg
|
||||
self.legIkFk(LLD, LB)
|
||||
|
||||
elif path in self._updateDict(LRD): # R leg
|
||||
self.legIkFk(LRD, LB)
|
||||
|
||||
|
||||
def armIKFk(self, _dict, armBase):
|
||||
switch = _dict[-1]['switch']
|
||||
ikCtrl, poleCtrl = _dict[0]['ik1'], _dict[0]['ik2']
|
||||
fkCtrl1, fkCtrl2, fkCtrl3 = _dict[1]['fk1'], _dict[1]['fk2'], _dict[1]['fk3']
|
||||
fti1, fti2, fti3 = _dict[2]['loc1'], _dict[2]['loc2'], _dict[2]['loc3']
|
||||
itf = _dict[3]['loc1']
|
||||
baseJ1, baseJ2, baseJ3, root = armBase['base1'], armBase['base2'], armBase['base3'], armBase['root']
|
||||
|
||||
# -------------------------------------------
|
||||
scaleValue = cmds.getAttr('{}.sx'.format(root))
|
||||
# -------------------------------------------
|
||||
|
||||
value = cmds.getAttr(switch + '.FKIKBlend')
|
||||
|
||||
self.autoKey([switch, ikCtrl, poleCtrl, fkCtrl1, fkCtrl2, fkCtrl3], self.prevTime)
|
||||
|
||||
# fk to ik
|
||||
if value == 0.0:
|
||||
upperV, midV = self.setIkStretch([fkCtrl1, fkCtrl2, fkCtrl3], [baseJ1, baseJ2, baseJ3])
|
||||
cmds.setAttr('{}.{}'.format(ikCtrl, 'Lenght1'), upperV / scaleValue)
|
||||
cmds.setAttr('{}.{}'.format(ikCtrl, 'Lenght2'), midV/ scaleValue)
|
||||
cmds.setAttr('{}.{}'.format(ikCtrl, 'stretchy'), 0)
|
||||
cmds.setAttr('{}.{}'.format(poleCtrl, 'lock'), 0)
|
||||
|
||||
cmds.matchTransform(ikCtrl, itf)
|
||||
self.setPolePose([fkCtrl1, fkCtrl2, fkCtrl3], poleCtrl)
|
||||
cmds.setAttr(switch + '.FKIKBlend', 10.0)
|
||||
|
||||
# ik to fk
|
||||
elif value == 10.0:
|
||||
for fk, loc in zip([fkCtrl1, fkCtrl2, fkCtrl3], [fti1, fti2, fti3]):
|
||||
cmds.matchTransform(fk, loc)
|
||||
|
||||
cmds.setAttr(switch + '.FKIKBlend', 0.0)
|
||||
|
||||
self.autoKey([switch, ikCtrl, poleCtrl, fkCtrl1, fkCtrl2, fkCtrl3], self.curTime)
|
||||
|
||||
def legIkFk(self, _dict, legBase):
|
||||
switch = _dict[-1]['switch']
|
||||
ikCtrl, poleCtrl, ik3, ik4, ik5, ik6 = _dict[0]['ik1'], _dict[0]['ik2'], _dict[0]['ik3'], _dict[0]['ik4'], _dict[0]['ik5'], _dict[0]['ik6']
|
||||
fkCtrl1, fkCtrl2, fkCtrl3, fkCtrl4 = _dict[1]['fk1'], _dict[1]['fk2'], _dict[1]['fk3'], _dict[1]['fk4']
|
||||
fti1, fti2, fti3, fti4, fti5 = _dict[2]['loc1'], _dict[2]['loc2'], _dict[2]['loc3'], _dict[2]['loc4'], _dict[2]['loc5']
|
||||
itf1, itf2 = _dict[3]['loc1'], _dict[3]['loc2']
|
||||
baseJ1, baseJ2, baseJ3, root = legBase['base1'], legBase['base2'], legBase['base3'], legBase['root']
|
||||
|
||||
# -------------------------------------------
|
||||
scaleValue = cmds.getAttr('{}.sx'.format(root))
|
||||
# -------------------------------------------
|
||||
|
||||
value = cmds.getAttr(switch + '.FKIKBlend')
|
||||
|
||||
self.autoKey([switch, ikCtrl, poleCtrl, ik3, ik4, ik5, ik6, fkCtrl1, fkCtrl2, fkCtrl3, fkCtrl4], self.prevTime)
|
||||
|
||||
# fk to ik
|
||||
if value == 0.0:
|
||||
upperV, midV = self.setIkStretch([fkCtrl1, fkCtrl2, fkCtrl3], [baseJ1, baseJ2, baseJ3])
|
||||
cmds.setAttr('{}.{}'.format(ikCtrl, 'Lenght1'), upperV / scaleValue)
|
||||
cmds.setAttr('{}.{}'.format(ikCtrl, 'Lenght2'), midV / scaleValue)
|
||||
cmds.setAttr('{}.{}'.format(ikCtrl, 'stretchy'), 0)
|
||||
cmds.setAttr('{}.{}'.format(poleCtrl, 'lock'), 0)
|
||||
|
||||
setAttrs([ikCtrl], ['swivel', 'roll', 'rock', 'antiPop'], [0, 0, 0, 0])
|
||||
setAttrs([ik3, ik4, ik5], ['t', 'r', 's'], [[0, 0, 0], [0, 0, 0], [1, 1, 1]])
|
||||
|
||||
cmds.matchTransform(ikCtrl, itf1)
|
||||
cmds.matchTransform(ik6, itf2) #
|
||||
self.setPolePose([fkCtrl1, fkCtrl2, fkCtrl3], poleCtrl)
|
||||
cmds.setAttr(switch + '.FKIKBlend', 10.0)
|
||||
|
||||
# ik to fk
|
||||
elif value == 10.0:
|
||||
for fk, loc in zip([fkCtrl1, fkCtrl2, fkCtrl3, fkCtrl4], [fti1, fti2, fti3, fti4]):
|
||||
cmds.matchTransform(fk, loc)
|
||||
|
||||
cmds.setAttr(switch + '.FKIKBlend', 0.0)
|
||||
|
||||
self.autoKey([switch, ikCtrl, poleCtrl, ik3, ik4, ik5, ik6, fkCtrl1, fkCtrl2, fkCtrl3, fkCtrl4], self.curTime)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
IkFk(False)
|
||||
|
||||
'''
|
||||
|
||||
Revision History
|
||||
Revision 1: 2023-08-11 : First publish
|
||||
Revision 2: 2023-08-14 : Fix a series of errors in the IK controller
|
||||
Revision 3: 2023-08-18 : Code Redundancy Optimization
|
||||
Revision 4: 2023-08-19 : Adding auto keyframe
|
||||
Revision 5: 2023-08-22 : Update IK Pole Position Algorithm
|
||||
Revision 6: 2023-09-15 : Optimize the transition from FK to IK
|
||||
|
||||
'''
|
||||
File diff suppressed because it is too large
Load Diff
466
2023/scripts/animation_tools/ikfx_switch.py
Normal file
466
2023/scripts/animation_tools/ikfx_switch.py
Normal file
@@ -0,0 +1,466 @@
|
||||
#!/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': '',
|
||||
'target_02': '',
|
||||
'target_03': '',
|
||||
'target_04': '',
|
||||
'target_05': '',
|
||||
'target_06': '',
|
||||
'target_07': '',
|
||||
'target_08': '',
|
||||
'target_09': '',
|
||||
'target_10': '',
|
||||
'target_11': '',
|
||||
'target_12': '',
|
||||
'target_13': ''
|
||||
}
|
||||
|
||||
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 Joint Root')
|
||||
self._create_text_field_row('target_08', '< IK Joint Mid')
|
||||
self._create_text_field_row('target_09', '< IK Joint End')
|
||||
cmds.separator(style='in', height=5)
|
||||
self._create_text_field_row('target_10', '< IK Ctrl Root')
|
||||
self._create_text_field_row('target_11', '< 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_12', '< Switch Ctrl')
|
||||
|
||||
# Special row for attribute selection
|
||||
cmds.rowLayout(numberOfColumns=2, adjustableColumn=1, columnWidth2=(45, 60))
|
||||
self.target_fields['target_13'] = 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_13'], edit=True, text=attr_name)
|
||||
self.target_values['target_13'] = attr_name
|
||||
except Exception as e:
|
||||
cmds.warning(f"Failed to load attribute: {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_joint_root = cmds.textField(self.target_fields['target_07'], query=True, text=True)
|
||||
ik_joint_mid = cmds.textField(self.target_fields['target_08'], query=True, text=True)
|
||||
ik_joint_end = cmds.textField(self.target_fields['target_09'], query=True, text=True)
|
||||
ik_ctrl_root = cmds.textField(self.target_fields['target_10'], query=True, text=True)
|
||||
ik_ctrl_pole = cmds.textField(self.target_fields['target_11'], query=True, text=True)
|
||||
switch_ctrl = cmds.textField(self.target_fields['target_12'], query=True, text=True)
|
||||
switch_attr = cmds.textField(self.target_fields['target_13'], query=True, text=True)
|
||||
|
||||
# Call the seamless switching function
|
||||
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
|
||||
)
|
||||
|
||||
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 = f"{switch_ctrl}_Switch_Locator"
|
||||
|
||||
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(f"{locator_name}.visibility", 0)
|
||||
|
||||
# Add IKFK_Seamless attribute if it doesn't exist
|
||||
if not cmds.objExists(f"{switch_ctrl}.IKFK_Seamless"):
|
||||
cmds.addAttr(switch_ctrl, longName="IKFK_Seamless", attributeType="enum", enumName="IK:FK:", keyable=False)
|
||||
cmds.setAttr(f"{switch_ctrl}.IKFK_Seamless", 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:
|
||||
if not cmds.objExists(f"{obj}.Location"):
|
||||
cmds.addAttr(obj, longName="Location", dataType="string", keyable=True)
|
||||
|
||||
# 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(f"{locator_name}.{attr_name}"):
|
||||
cmds.addAttr(locator_name, longName=attr_name, dataType="string", keyable=True)
|
||||
|
||||
cmds.setAttr(f"{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(f"{target_obj}.Location"):
|
||||
if not cmds.isConnected(f"{locator_name}.{attr_name}", f"{target_obj}.Location"):
|
||||
cmds.connectAttr(f"{locator_name}.{attr_name}", f"{target_obj}.Location", force=True)
|
||||
|
||||
# Create switching script
|
||||
create_switching_script()
|
||||
|
||||
# Create script job for this control
|
||||
create_script_job(switch_ctrl, locator_name)
|
||||
|
||||
print(f"IKFK Seamless Switching setup completed for {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(f"{script_node_name}.scriptType", 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 = f"{switch_ctrl}_Switching_Script"
|
||||
|
||||
# MEL script for script job creation
|
||||
mel_script = f'''
|
||||
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=="{locator_name}"){{
|
||||
string $Switch[]=`listConnections ($Locator+".Switch_Ctrl")`;
|
||||
scriptJob -ac ($Switch[0]+".IKFK_Seamless") ("sg_switching "+$Locator) -kws;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
'''
|
||||
|
||||
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(f"{script_node_name}.scriptType", 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()
|
||||
@@ -49,62 +49,27 @@ global proc shelf_Nexus_Animation () {
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "IK/FK Switcher - Build temporary IK/FK setup on any rig"
|
||||
-annotation "IKFK Switch - Seamless switching between IK and FK animation"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "IK/FK"
|
||||
-label "IKFK"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "IK/FK"
|
||||
-imageOverlayLabel "IKFK"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "ik_fk_switcher.png"
|
||||
-image1 "ik_fk_switcher.png"
|
||||
-image "ikfk_switch.png"
|
||||
-image1 "ikfk_switch.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "import animation_tools.ik_fk_switcher\nanimation_tools.ik_fk_switcher.user_interface()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
shelfButton
|
||||
-enableCommandRepeat 1
|
||||
-flexibleWidthType 3
|
||||
-flexibleWidthValue 32
|
||||
-enable 1
|
||||
-width 35
|
||||
-height 34
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "Advanced IK/FK Switch - Switch between IK and FK for ADV rigs"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "AdvIKFK"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "AdvIKFK"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "advIkFkSwitch.png"
|
||||
-image1 "advIkFkSwitch.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "import animation_tools.advIkFkSwitch\nanimation_tools.advIkFkSwitch.IkFk(False)"
|
||||
-command "import animation_tools.ikfx_switch\nanimation_tools.ikfx_switch.show()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
144
2025/scripts/animation_tools/IKFK_SWITCH_README.md
Normal file
144
2025/scripts/animation_tools/IKFK_SWITCH_README.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# IKFK Switch Tool
|
||||
|
||||
## 概述
|
||||
IKFK Switch Tool 是一个用于在Maya中实现IK和FK动画之间无缝切换的工具。该工具从原始MEL脚本转换为Python版本,提供更好的跨版本兼容性。
|
||||
|
||||
## 版本信息
|
||||
- **版本**: V1.0
|
||||
- **兼容性**: Maya 2018+
|
||||
- **语言**: Python
|
||||
- **原始版本**: MEL (ikfk_switch.mel)
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 主要功能
|
||||
1. **无缝切换**: 在IK和FK动画之间进行无缝切换,保持动画连续性
|
||||
2. **自动匹配**: 切换时自动匹配控制器位置和旋转
|
||||
3. **关键帧设置**: 自动在切换点设置关键帧
|
||||
4. **预设支持**: 内置ADV绑定预设,支持快速设置
|
||||
|
||||
### UI功能
|
||||
- **FK控制器加载**: 加载FK骨骼和控制器
|
||||
- FK Joint Root/Mid/End
|
||||
- FK Ctrl Root/Mid/End
|
||||
|
||||
- **IK控制器加载**: 加载IK骨骼和控制器
|
||||
- IK Joint Root/Mid/End
|
||||
- IK Ctrl Root (IK手柄)
|
||||
- IK Ctrl Pole (极向量控制器)
|
||||
|
||||
- **切换控制器**: 设置切换属性
|
||||
- Switch Ctrl (切换控制器)
|
||||
- Switch Attr (切换属性名称)
|
||||
|
||||
### 辅助功能
|
||||
- **ADV Build**: 快速为ADV绑定设置IKFK切换
|
||||
- **Empty**: 清空所有输入字段
|
||||
- **BiliBili**: 打开教程链接
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本使用流程
|
||||
|
||||
1. **加载FK控制器**
|
||||
- 选择FK骨骼根节点,点击"< FK Joint Root"
|
||||
- 选择FK骨骼中间节点,点击"< FK Joint Mid"
|
||||
- 选择FK骨骼末端节点,点击"< FK Joint End"
|
||||
- 重复以上步骤加载FK控制器
|
||||
|
||||
2. **加载IK控制器**
|
||||
- 选择IK骨骼节点,点击相应按钮
|
||||
- 选择IK控制器和极向量控制器
|
||||
|
||||
3. **设置切换控制器**
|
||||
- 选择用于切换的控制器,点击"< Switch Ctrl"
|
||||
- 在通道盒中选择切换属性,点击"< Switch Attr"
|
||||
|
||||
4. **构建切换系统**
|
||||
- 点击"<<< Build Seamless Switching >>>"按钮
|
||||
- 系统会自动创建必要的属性和脚本节点
|
||||
|
||||
5. **使用切换功能**
|
||||
- 在切换控制器上找到"IKFK_Seamless"属性
|
||||
- 切换该属性值(IK=0, FK=1)即可实现无缝切换
|
||||
|
||||
### ADV绑定快速设置
|
||||
|
||||
如果使用ADV绑定系统,可以使用预设功能:
|
||||
1. 展开"Edit"面板
|
||||
2. 点击"<<< ADV Build >>>"
|
||||
3. 系统会自动为左右手臂和腿部设置IKFK切换
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心组件
|
||||
|
||||
1. **IKFKSwitchUI类**: UI界面管理
|
||||
- 创建和管理UI窗口
|
||||
- 处理用户输入
|
||||
- 调用切换逻辑
|
||||
|
||||
2. **seamless_switching函数**: 核心切换逻辑
|
||||
- 创建数据存储locator
|
||||
- 添加必要属性
|
||||
- 建立属性连接
|
||||
- 创建脚本节点
|
||||
|
||||
3. **create_switching_script函数**: 创建全局切换脚本
|
||||
- 使用MEL脚本实现切换逻辑
|
||||
- 处理约束和关键帧
|
||||
|
||||
4. **create_script_job函数**: 创建脚本任务
|
||||
- 监听属性变化
|
||||
- 自动触发切换
|
||||
|
||||
### 兼容性设计
|
||||
|
||||
- 使用`maya.cmds`和`maya.mel`模块,确保跨版本兼容
|
||||
- 避免使用版本特定的API
|
||||
- 使用f-string格式化(Python 3.6+)
|
||||
- 异常处理确保稳定性
|
||||
|
||||
## 工具架集成
|
||||
|
||||
该工具已集成到Nexus Animation工具架:
|
||||
- **图标**: ikfk_switch.png
|
||||
- **位置**: 动画工具架末尾
|
||||
- **调用**: `import animation_tools.ikfx_switch; animation_tools.ikfx_switch.show()`
|
||||
|
||||
## 文件位置
|
||||
|
||||
### Maya 2023
|
||||
- 脚本: `scripts/animation_tools/ikfx_switch.py`
|
||||
- 图标: `icons/ikfk_switch.png`
|
||||
- 工具架: `shelves/shelf_Nexus_Animation.mel`
|
||||
|
||||
### Maya 2025
|
||||
- 脚本: `scripts/animation_tools/ikfx_switch.py`
|
||||
- 图标: `icons/ikfk_switch.png`
|
||||
- 工具架: `shelves/shelf_Nexus_Animation.mel`
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **属性要求**: 切换属性必须有最小值和最大值设置
|
||||
2. **命名规范**: 建议使用清晰的命名规范以便识别
|
||||
3. **关键帧**: 切换会在当前帧和前一帧设置关键帧
|
||||
4. **选择保持**: 切换后会恢复原始选择
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **切换不工作**
|
||||
- 检查所有必需的对象是否存在
|
||||
- 确认切换属性已正确设置
|
||||
- 查看脚本编辑器的错误信息
|
||||
|
||||
2. **位置不匹配**
|
||||
- 确保FK和IK骨骼层级正确
|
||||
- 检查约束设置
|
||||
- 验证控制器方向
|
||||
|
||||
3. **属性加载失败**
|
||||
- 确保在通道盒中选择了属性
|
||||
- 检查属性是否可关键帧化
|
||||
File diff suppressed because it is too large
Load Diff
466
2025/scripts/animation_tools/ikfx_switch.py
Normal file
466
2025/scripts/animation_tools/ikfx_switch.py
Normal file
@@ -0,0 +1,466 @@
|
||||
#!/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': '',
|
||||
'target_02': '',
|
||||
'target_03': '',
|
||||
'target_04': '',
|
||||
'target_05': '',
|
||||
'target_06': '',
|
||||
'target_07': '',
|
||||
'target_08': '',
|
||||
'target_09': '',
|
||||
'target_10': '',
|
||||
'target_11': '',
|
||||
'target_12': '',
|
||||
'target_13': ''
|
||||
}
|
||||
|
||||
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 Joint Root')
|
||||
self._create_text_field_row('target_08', '< IK Joint Mid')
|
||||
self._create_text_field_row('target_09', '< IK Joint End')
|
||||
cmds.separator(style='in', height=5)
|
||||
self._create_text_field_row('target_10', '< IK Ctrl Root')
|
||||
self._create_text_field_row('target_11', '< 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_12', '< Switch Ctrl')
|
||||
|
||||
# Special row for attribute selection
|
||||
cmds.rowLayout(numberOfColumns=2, adjustableColumn=1, columnWidth2=(45, 60))
|
||||
self.target_fields['target_13'] = 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_13'], edit=True, text=attr_name)
|
||||
self.target_values['target_13'] = attr_name
|
||||
except Exception as e:
|
||||
cmds.warning(f"Failed to load attribute: {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_joint_root = cmds.textField(self.target_fields['target_07'], query=True, text=True)
|
||||
ik_joint_mid = cmds.textField(self.target_fields['target_08'], query=True, text=True)
|
||||
ik_joint_end = cmds.textField(self.target_fields['target_09'], query=True, text=True)
|
||||
ik_ctrl_root = cmds.textField(self.target_fields['target_10'], query=True, text=True)
|
||||
ik_ctrl_pole = cmds.textField(self.target_fields['target_11'], query=True, text=True)
|
||||
switch_ctrl = cmds.textField(self.target_fields['target_12'], query=True, text=True)
|
||||
switch_attr = cmds.textField(self.target_fields['target_13'], query=True, text=True)
|
||||
|
||||
# Call the seamless switching function
|
||||
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
|
||||
)
|
||||
|
||||
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 = f"{switch_ctrl}_Switch_Locator"
|
||||
|
||||
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(f"{locator_name}.visibility", 0)
|
||||
|
||||
# Add IKFK_Seamless attribute if it doesn't exist
|
||||
if not cmds.objExists(f"{switch_ctrl}.IKFK_Seamless"):
|
||||
cmds.addAttr(switch_ctrl, longName="IKFK_Seamless", attributeType="enum", enumName="IK:FK:", keyable=False)
|
||||
cmds.setAttr(f"{switch_ctrl}.IKFK_Seamless", 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:
|
||||
if not cmds.objExists(f"{obj}.Location"):
|
||||
cmds.addAttr(obj, longName="Location", dataType="string", keyable=True)
|
||||
|
||||
# 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(f"{locator_name}.{attr_name}"):
|
||||
cmds.addAttr(locator_name, longName=attr_name, dataType="string", keyable=True)
|
||||
|
||||
cmds.setAttr(f"{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(f"{target_obj}.Location"):
|
||||
if not cmds.isConnected(f"{locator_name}.{attr_name}", f"{target_obj}.Location"):
|
||||
cmds.connectAttr(f"{locator_name}.{attr_name}", f"{target_obj}.Location", force=True)
|
||||
|
||||
# Create switching script
|
||||
create_switching_script()
|
||||
|
||||
# Create script job for this control
|
||||
create_script_job(switch_ctrl, locator_name)
|
||||
|
||||
print(f"IKFK Seamless Switching setup completed for {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(f"{script_node_name}.scriptType", 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 = f"{switch_ctrl}_Switching_Script"
|
||||
|
||||
# MEL script for script job creation
|
||||
mel_script = f'''
|
||||
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=="{locator_name}"){{
|
||||
string $Switch[]=`listConnections ($Locator+".Switch_Ctrl")`;
|
||||
scriptJob -ac ($Switch[0]+".IKFK_Seamless") ("sg_switching "+$Locator) -kws;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
'''
|
||||
|
||||
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(f"{script_node_name}.scriptType", 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()
|
||||
@@ -49,27 +49,27 @@ global proc shelf_Nexus_Animation () {
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "IK/FK Switcher - Build temporary IK/FK setup on any rig"
|
||||
-annotation "IKFK Switch - Seamless switching between IK and FK animation"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "IK/FK"
|
||||
-label "IKFK"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "IK/FK"
|
||||
-imageOverlayLabel "IKFK"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "ik_fk_switcher.png"
|
||||
-image1 "ik_fk_switcher.png"
|
||||
-image "ikfk_switch.png"
|
||||
-image1 "ikfk_switch.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "import animation_tools.ik_fk_switcher\nanimation_tools.ik_fk_switcher.user_interface()"
|
||||
-command "import animation_tools.ikfx_switch\nanimation_tools.ikfx_switch.show()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
@@ -249,5 +249,4 @@ global proc shelf_Nexus_Animation () {
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user