diff --git a/2023/icons/ik_fk_switcher.png b/2023/icons/ikfk_switch.png similarity index 100% rename from 2023/icons/ik_fk_switcher.png rename to 2023/icons/ikfk_switch.png diff --git a/2023/scripts/animation_tools/IKFK_SWITCH_README.md b/2023/scripts/animation_tools/IKFK_SWITCH_README.md new file mode 100644 index 0000000..4f455a9 --- /dev/null +++ b/2023/scripts/animation_tools/IKFK_SWITCH_README.md @@ -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 diff --git a/2023/scripts/animation_tools/advIkFkSwitch.py b/2023/scripts/animation_tools/advIkFkSwitch.py deleted file mode 100644 index df5af10..0000000 --- a/2023/scripts/animation_tools/advIkFkSwitch.py +++ /dev/null @@ -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 - -''' \ No newline at end of file diff --git a/2023/scripts/animation_tools/ik_fk_switcher.py b/2023/scripts/animation_tools/ik_fk_switcher.py deleted file mode 100644 index 2598c7b..0000000 --- a/2023/scripts/animation_tools/ik_fk_switcher.py +++ /dev/null @@ -1,1700 +0,0 @@ -""" -You can use this script for any commercial or non-commercial projects. You're not allowed to sell this script. -Author - Petar3D -Initial Release Date - 10.05.2023 -Version - 1.0 (10.05.2023) -Version - 2.0 (02.11.2023) -Version - 3.0 (12.03.2025) - Current - -Description - Tool that allows you to build a temporary IK/FK setup on any rig, while preserving animation. Example - You select the controls for you arm FK chain (Shoulder, Elbow, Wrist), -and then click the "FK to IK" button to apply an IK setup on top of your original FK controls. You can also isolate the code from the UI so you can put it into a marking menu or on the shelf. -""" - -import maya.cmds as cmds -import maya.mel as mel -import maya.OpenMaya as om -#from math import isclose -# from sys import exit # Removed to avoid SystemExit error - -################################################################################################################################################################################################################## - -# GENERAL FUNCTIONS -def get_timeline_start_end(): # ---------- DONE - timeline_start = cmds.playbackOptions(min=True, q=True) - timeline_end = cmds.playbackOptions(max=True, q=True) - return timeline_start, timeline_end - -def filter_list_unpack_brackets(*list): # IF LIST HAS 1 ITEM, WE RETURN IT AS ITEM[0] TO REMOVE THE BRACKETS - new_list = [item if len(item) > 1 else item[0] for item in list] - return new_list - -def create_control(name): - points = [(-1, 0, -0), (1, 0, 0), (0, 0, 0), (0, 0, -1), (0, 0, 1), (0, 0, 0), (0, -1, 0), (0, 1, 0)] - temp_control = cmds.curve(n=name, d=1, p=points) - return temp_control - -def apply_euler_filter(controls): - apply_key_reducer = cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True) - key_reducer_intensity = cmds.floatFieldGrp("Intensity_FloatField", q=True, v1=True) - for control in controls: - cmds.select(control) - cmds.filterCurve(control + ".translate", control + ".rotate", f="euler") - if apply_key_reducer: - cmds.filterCurve(control + ".translate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity) - cmds.filterCurve(control + ".rotate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity + 10) - -def hide_attributes(type, *controls): # ---------- DONE - for item in controls: - for attr in ["." + type + "X", "." + type + "Y", "." + type + "Z"]: - cmds.setAttr(item + attr, k=False, l=True, cb=False) - -def hide_controls(*controls): # ---------- DONE - for control in controls: - cmds.setAttr(control + ".v", 0) - -def set_control_color(objects, color): - for obj in objects: - cmds.setAttr(obj + ".overrideEnabled", 1) - cmds.setAttr(obj + ".overrideColor", color) - -def set_control_thickness(objects, value): - for obj in objects: - cmds.setAttr(obj + ".lineWidth", value) - -def set_form_layout_coordinates(form_layout, name, top_coordinates, left_coordinates): # ---------- DONE - #ADJUSTS THE POSITION OF THE UI FEATURES - cmds.formLayout(form_layout, edit=True, attachForm=[(name, "top", top_coordinates), (name, "left", left_coordinates)]) - -def documentation(): - cmds.showHelp("https://petarpehchevski3d.gumroad.com/l/ikfkswitcher", a=True) - -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="" + message + "", pos='midCenter', fade=True, fst=time, ck=True) - # Also display warning in Script Editor - cmds.warning(message) - if to_exit: - return # Do not use exit() to avoid SystemExit error - -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 - temp_attribute = [] - if constraint_type == "orient": - temp_attribute = "rotate" - elif constraint_type == "point": - temp_attribute = "translate" - elif constraint_type == "parent": - temp_attribute = ["translate", "rotate"] - return temp_attribute - -def get_locked_attributes(control, attribute): # ---------- OPTIMIZE - #CHECK WHICH ATTRIBUTES ON THE CONTROL ARE LOCKED, SO AS TO KNOW WHICH ONES TO SKIP WHEN APPLYING CONSTRAINTS - if attribute == "parent": - translate_attributes = [".translateX", ".translateY", ".translateZ"] - rotate_attributes = [".rotateX", ".rotateY", ".rotateZ"] - - locked_translate = [attr.lower()[-1:] for attr in translate_attributes if cmds.getAttr(control + attr, lock=True)] - locked_rotate = [attr.lower()[-1:] for attr in rotate_attributes if cmds.getAttr(control + attr, lock=True)] - - locked_attributes = [locked_translate, locked_rotate] - else: - attributes = ["." + attribute + "X", "." + attribute + "Y", "." + attribute + "Z"] - locked_attributes = [attr.lower()[-1:] for attr in attributes if cmds.getAttr(control + attr, lock=True)] - - return locked_attributes - -def constraint(parent, child, type, mo=True): # ---------- DONE - #CONSTRAINT SYSTEM - try: - locked_attributes = get_locked_attributes(child, type) - if type == "parent": - constraint = cmds.parentConstraint(parent, child, maintainOffset = mo, skipTranslate = locked_attributes[0], skipRotate = locked_attributes[1]) - if type == "translate": - constraint = cmds.pointConstraint(parent, child, maintainOffset = mo, skip = locked_attributes) - if type == "rotate": - constraint = cmds.orientConstraint(parent, child, maintainOffset = mo, skip = locked_attributes) - except RuntimeError: - assist_message("Error: Your selected controls are already being influenced by some other source.", 5000) - - return constraint[0] - -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) - -def get_timeline_range(): - aTimeSlider = mel.eval('$tmpVar=$gPlayBackSlider') - timeline_start, timeline_end = cmds.timeControl(aTimeSlider, q=True, rangeArray=True) - if 1 < (timeline_end - timeline_start): - to_isolate_timeline = True - timeline_end = timeline_end - 1 - else: - to_isolate_timeline = False - timeline_start, timeline_end = get_timeline_start_end() - - return timeline_start, timeline_end, to_isolate_timeline - -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### -def divide_influence(curve, first_value, second_value): - cmds.setKeyframe(curve, t=(timeline_start, timeline_end), value=first_value) - cmds.setKeyframe(curve, t=(timeline_start - 1, timeline_end + 1), value=second_value) - -def get_constraint_blend_index(constraint, control): - # WE'RE TRYING TO FIND THE INDEX AT THE END OF THE CONSTRAINT'S WEIGHT ATTRIBUTE. BECAUSE THERE COULD BE MANY CONSTRAINTS APPLIED ON THE SAME OBJECT, WE CAN'T ALWAYS KNOW WHAT THAT NUMBER WILL BE - for item in cmds.listConnections(constraint, c=True): - if "{0}.{1}W".format(constraint, control) in item: - constraint_index = item[-1:] - blend_index = constraint[-1:] - return constraint_index, blend_index - -def check_if_referenced(original_control, temp_control): - # IF THE RIG IS REFERENCED, WE STORE THE NAME OF THE TEMP LOCATOR WITHOUT THE NAMESPACE, BECAUSE THE CONSTRAINT WE'LL INFLUENCE DON'T HAVE THE NAMESPACE INSIDE - if cmds.referenceQuery(original_control, isNodeReferenced=True) or ":" in original_control: - temp_control = temp_control.split(":")[-1:][0] - return temp_control - -def set_key_frame(constraint_type, control): - temp_attribute = get_constraint_attribute(constraint_type.lower()) - # CHECKS TO SEE IF ORIGINAL CONTROL HAS ANY KEYS ON CURVES ALREADY, IF NOT IT PLACES THEM TO ACTIVATE THE BLEND INDEX - if cmds.keyframe(control, at=temp_attribute, q=True) == None: - cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute) - else: - cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute, i=True) - -def isolate_constraint(constraints, temp_controls, original_controls, constraint_type): - for constraint, temp_control, original_control in zip(constraints, temp_controls, original_controls): - temp_control = check_if_referenced(original_control, temp_control) - - set_key_frame(constraint_type, original_control) - - constraint_index, blend_index = get_constraint_blend_index(constraint, temp_control) - divide_influence(constraint + "." + temp_control + "W" + constraint_index, 1, 0) - divide_influence(original_control + ".blend" + constraint_type + blend_index, 1, 0) - -def isolate_visibility(controls, first_value, second_value): - for control in controls: - temp_shape_nodes = cmds.listRelatives(control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - divide_influence(temp_shape_node + ".v", first_value, second_value) - -def delete_visibility_keys(*original_controls): - if influence_visibility: - for original_control in original_controls: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys") - -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### - -######### CLEAR REDUNDANT KEYS ########## - -def remove_unnecessary_keys(controls, attributes): - tolerance = 0.000002 - for control in controls: - for attr in attributes: - for frame in range(int(timeline_start) + 1, int(timeline_end)): - current_value = cmds.keyframe(control, q=True, t=(frame, frame), at=attr, eval=True)[0] - previous_value = cmds.keyframe(control, q=True, t=(frame - 1, frame - 1), at=attr, eval=True)[0] - #next_value = cmds.keyframe(control, q=True, t=(frame + 1, frame + 1), at=attr, eval=True)[0] - if abs(current_value - previous_value) <= max(1e-09 * max(abs(current_value), abs(previous_value)), tolerance): - cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys") - - # if isclose(current_value, previous_value, abs_tol=tolerance): - # cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys") - - -################# APPLY TO BOTH ############## -def check_if_setup_exists(controls): # DONE - for control in controls: - if cmds.objExists(control + "_temp_IK_Name*") or cmds.objExists(control + "_temp_FK_Name*"): - assist_message("Error: One or more selected controls are part of a pre-existing setup somewhere else on the timeline.", 5000) - -def create_name_reference_groups(contents, suffix): # ---------- DONE - # #CREATES EMPTY GROUPS AS PROXIES FOR REFERENCING THE ACTUAL CONTROLS LATER ON - timeline_mode_keyword = "ISR" if specific_timeline_mode == True else "NIF" - - # BECAUSE SOME NAMES HAVE DASH IN THE MIDDLE IF THEY'RE NOT UNIQUE NAMES, WE HAVE TO REPLACE IT WITH A NEW KEYWORD "_SEPARATOR_" FOR WHEN REFERENCING IT BACK WHEN BAKING. - new_contents = [obj.replace("|", "_dash_") if "|" in obj else obj for obj in contents] - reference_grps = [] - # WE HAD TO DO A CHANGE HERE BECAUSE IF YOUR PROJECT HAS NAMESPACES, IF YOU CREATE A GROUP NODE, IT'LL INHERIT FROM ALL THE NAMESPACES. THIS WAS PROBLEMTIC. I NEED THE GROUP NAMES TO BE IDENTICAL TO THE CONTROL NAME. SO I JUST GREATE THE GROUP, AND RENAME - for obj in new_contents: - temp_group = cmds.group(n=obj, em=True) - temp_group = cmds.rename(temp_group, obj + suffix + "_" + timeline_mode_keyword + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - reference_grps.append(temp_group) - return reference_grps - -def create_temp_grp(suffix, control_name, controls_to_group): # DONE - temp_group = cmds.group(*controls_to_group, n=control_name + suffix) - cmds.rename(temp_group, control_name + suffix) - #cmds.lockNode(temp_group) - -def match_rotation_order(parent, child): # ---------- DONE - # MATCHES THE ROTATION ORDER FROM THE ORIGINAL SELECTIONS ONTO THE TEMPORARY SETUP CONTROLS - original_rotation_order = cmds.getAttr(parent + ".rotateOrder") - cmds.setAttr(child + ".rotateOrder", original_rotation_order) - -def match_control_scale(parent, children): - # SCALE UP AN OBJECT TO ANOTHER ONE'S BOUNDING BOX SCALE, INCASE IT'S BEEN FREEZE-TRANSFORMED. THIS WAY THE USER DOESN'T HAVE TO MANUALLY ADJUST THE SIZE - children = cmds.ls(children, flatten=True) - if cmds.objectType(parent) == "joint": - parentShapeNode = parent - else: - parentShapeNode = cmds.listRelatives(parent, shapes=True, children=True, pa=True)[0] - - xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(parentShapeNode) - parentDistanceX, parentDistanceY, parentDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin] - - for child in children: - xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(child) - childDistanceX, childDistanceY, childDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin] - - # WE QUERY THE ORIGINAL SCALE OF THE LOCATOR - originalX, originalY, originalZ = cmds.xform(child, q=True, s=True, r=True) - - divisionX, divisionY, divisionZ = [parentDistanceX / childDistanceX, parentDistanceY / childDistanceY, parentDistanceZ / childDistanceZ] - - # WE GET THE FINAL SCALE HERE, WE TAKE THE LONGEST NUMBER AND APPLY THAT TO ALL SCALE AXIS - largestAxis = max([originalX * divisionX, originalY * divisionY, originalZ * divisionZ]) * 3 - newScale = [largestAxis, largestAxis, largestAxis] - if cmds.objectType(parent) == "joint": - if cmds.currentUnit(q=True) == "cm": - cmds.xform(child, scale=(40, 40, 40)) - else: - cmds.xform(child, scale=(0.7, 0.7, 0.7)) - else: - cmds.xform(child, scale=newScale) - - -def check_anim_layer(controls): - root_layer = cmds.animLayer(q=True, r=True) - if root_layer: - layer_list = cmds.animLayer(root_layer, q=True, c=True) - if layer_list != None: - for layer in layer_list: - attribute_list = cmds.animLayer(layer, q=True, attribute=True) - if attribute_list != None: - for attr in attribute_list: - for obj in controls: - if obj in attr: - assist_message("Warning: A control you've selected is in an anim layer. This may mess up the baking process and give bad results", 6000, False) - -################# APPLY TO BOTH ############## - -############# CREATE IK CONTROLS ################# -def get_original_fk_controls(): # ---------- DONE - fk_ctrls = cmds.ls(sl=True) - if len(fk_ctrls) != 3: - assist_message("Incorrect number of controls selected. To apply an IK setup, you need to select 3 FK controls, in order of parent to child.", 4000) - return None - cmds.select(cl=True) - return fk_ctrls - -def create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl): # ---------- DONE - parent_jnt = cmds.joint(n=parent_ctrl + "_temp_jnt") - middle_jnt = cmds.joint(n=middle_ctrl + "_temp_jnt") - child_jnt = cmds.joint(n=child_ctrl + "_temp_jnt") - - return parent_jnt, middle_jnt, child_jnt - -def create_ik_controls(middle_ctrl, child_ctrl): # ---------- DONE - ik_ctrl = create_control(child_ctrl + "_ik_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - pole_vector_ctrl = create_control(middle_ctrl + "_pole_vector_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - match_control_scale(child_ctrl, [ik_ctrl, pole_vector_ctrl]) - - return ik_ctrl, pole_vector_ctrl - - -def create_ik_handle(parent_jnt, child_jnt, ik_ctrl): # ---------- DONE - # SETS PREFERRED ANGLE ON THE TEMP JOINT CHAIN, APPLIES AN IK HANDLE ON IT - cmds.joint(parent_jnt, e=True, spa=True, ch=True) - temp_IK_Handle = cmds.ikHandle(n=ik_ctrl + "_ikHandle1", sj=parent_jnt, ee=child_jnt)[0] - cmds.matchTransform(temp_IK_Handle, child_jnt) - return temp_IK_Handle - -def create_temp_ik_controls(): # DONE - # STORES ORIGINAL SELECTED FK CONTROLS INTO VARIABLES - fk_ctrls = get_original_fk_controls() - if fk_ctrls is None: - return None - parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls - check_if_setup_exists([parent_ctrl, middle_ctrl, child_ctrl]) - check_anim_layer(fk_ctrls) - - delete_visibility_keys(parent_ctrl, middle_ctrl, child_ctrl) - # CREATES TEMPORARY IK JOINTS AND CONTROLS - parent_jnt, middle_jnt, child_jnt = ik_jnts = create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl) - ik_ctrl, pole_vector_ctrl = ik_ctrls = create_ik_controls(middle_ctrl, child_ctrl) - - # CREATES EMPTY GROUPS FOR REFERENCING THE ORIGINAL CONTROLS WHEN BAKING/DELETING SETUPS - ik_reference_grps = create_name_reference_groups([parent_ctrl, middle_ctrl, child_ctrl], "_temp_IK_Name") - - # STORES WHOLE SETUP IN GROUP - create_temp_grp("_temp_IK_Group", parent_ctrl, [parent_jnt, ik_ctrl, pole_vector_ctrl, ik_reference_grps]) - - return fk_ctrls, ik_jnts, ik_ctrls -############# CREATE IK CONTROLS ################# - - - -############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ################## - -# SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CONTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS -def create_reverse_constraint_setup_ik(parent, child): - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - match_rotation_order(parent, child) - - # POSITIONS THE NEW CONTROLS - if "pole_vector_ctrl" not in child: ################## DON'T LIKE HOW I HAVE TO ADD THESE EXTRA CONDITIONALS IN HERE ##################### - cmds.matchTransform(child, parent, position=True, rotation=True) - - if "jnt" in child: - cmds.makeIdentity(child, apply=True, t=True, r=True, s=True) - - # CONSTRAINTS ORIGINAL CONTROLS TO TEMPORARY, BAKES THE TEMP TO INHERIT ANIM DATA, DELETES CONSTRAINT, CONSTRAINTS TEMPORARY TO ORIGINAL - temp_constraint = constraint(parent, child, "parent") - cmds.bakeResults(child, t=(timeline_start, timeline_end), sb=bake_interval) - cmds.delete(temp_constraint) - - if "pole_vector_ctrl" not in child: - temp_constraint = constraint(child, parent, "rotate") - - return temp_constraint - -def get_vector(position): # ---------- DONE - return om.MVector(position[0], position[1], position[2]) - -def get_pole_vector_position(parent_jnt, middle_jnt, child_jnt): # ---------- DONE - vector_parent = get_vector(cmds.xform(parent_jnt, q=True, t=True, ws=True)) - vector_middle = get_vector(cmds.xform(middle_jnt, q=True, t=True, ws=True)) - vector_child = get_vector(cmds.xform(child_jnt, q=True, t=True, ws=True)) - - parent_to_child_vector = (vector_child - vector_parent) - parent_to_middle_vector = (vector_middle - vector_parent) - - scale_value = (parent_to_child_vector * parent_to_middle_vector) / (parent_to_child_vector * parent_to_child_vector) - parent_to_child_middle_point = parent_to_child_vector * scale_value + vector_parent - - parent_to_middle_length = (vector_middle - vector_parent).length() - middle_to_child_length = (vector_child - vector_middle).length() - total_length = parent_to_middle_length + middle_to_child_length - - pole_vector_position = (vector_middle - parent_to_child_middle_point).normal() * total_length + vector_middle - - return pole_vector_position - -def set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl): - for frame in range(int(timeline_start), int(timeline_end) + 1): - cmds.currentTime(int(frame)) - pole_vector_position = get_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl) - cmds.move(pole_vector_position.x, pole_vector_position.y, pole_vector_position.z, pole_vector_ctrl) - cmds.setKeyframe(pole_vector_ctrl, time=(frame, frame), at="translate") - - -def set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls): - parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls - ik_ctrl, pole_vector_ctrl = ik_ctrls - parent_jnt, middle_jnt, child_jnt = ik_jnts - - # JOINTS - temp_constraint = [create_reverse_constraint_setup_ik(parent, child) for parent, child in zip(fk_ctrls, ik_jnts)] - - # IK CTRL - ik_orient_constraint = create_reverse_constraint_setup_ik(child_jnt, ik_ctrl) - - # POLE-VECTOR - set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl) - #create_reverse_constraint_setup_ik(middle_jnt, pole_vector_ctrl) - - return temp_constraint, ik_orient_constraint -############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ################## - - -#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) - cmds.autoKeyframe(state=True) - - timeline_start, timeline_end, specific_timeline_mode = get_timeline_range() - check_negative_time_range(timeline_start, timeline_end) - ###### CREATE CONTROLS ###### - result = create_temp_ik_controls() - if result is None: - return - [parent_ctrl, middle_ctrl, child_ctrl], [parent_jnt, middle_jnt, child_jnt], [ik_ctrl, pole_vector_ctrl] = fk_ctrls, ik_jnts, ik_ctrls = result - ###### REPOSITION CONTROLS ###### - temp_constraints, ik_orient_constraint = set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls) - - # CREATES IK HANDLE, ADDS POLE VECTOR CONSTRAINTz - cmds.currentTime(timeline_start) - temp_ik_handle = create_ik_handle(parent_jnt, child_jnt, ik_ctrl) - cmds.parent(temp_ik_handle, ik_ctrl, s=True) - pole_vector_constraint = cmds.poleVectorConstraint(pole_vector_ctrl, temp_ik_handle) - - # MAKES THE IK SETUP WORLD-SPACE - WHEN SHOULDER ROTATES THE IK TRANSLATES BUT DOESN'T ROTATE - point_constraint = constraint(parent_ctrl, parent_jnt, "translate") - - ####### ISOLATE VISIBILITY ###### - if specific_timeline_mode: - if influence_visibility: - isolate_visibility([parent_ctrl, middle_ctrl, child_ctrl], 0, 1) - isolate_visibility([ik_ctrl, pole_vector_ctrl], 1, 0) - ####### ISOLATE CONSTRAINT ###### - isolate_constraint(temp_constraints, ik_jnts, fk_ctrls, "Orient") - else: - if influence_visibility: - for original_control in fk_ctrls: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True: - cmds.setAttr(temp_shape_node + ".v", 0) - - - #isolate_constraint([ik_orient_constraint], [ik_ctrl], [child_jnt], "Orient") - #isolate_constraint([point_constraint], [parent_ctrl], [parent_jnt], "Point") - - ############################# - DONE - ############################# - - #CLEAN-UP # - COULD MAKE INTO ITS OWN FUNCTION - hide_controls(parent_jnt, temp_ik_handle) - cmds.cutKey(pole_vector_ctrl, t=(timeline_start, timeline_end), at="rotate", option="keys") - hide_attributes("rotate", pole_vector_ctrl) - hide_attributes("scale", ik_ctrl, pole_vector_ctrl) - set_control_color(ik_ctrls, 18) - set_control_thickness(ik_ctrls, 2) - #set_control_thickness([ik_ctrl, pole_vector_ctrl], 2) - #cmds.keyTangent(ik_ctrls, e=True, itt="auto", ott="auto") - apply_euler_filter(ik_ctrls) - if clear_keys: - remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"]) - remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"]) - - cmds.select(ik_ctrl) - - -################ GET ORIGINAL CONTROLS #################### -def get_original_ik_controls(): - ik_ctrls = cmds.ls(sl=True) - if len(ik_ctrls) != 2: - assist_message("Incorrect number of controls selected. To apply an FK setup, you need to select the Pole Vector first and then the IK Control, in order.", 4000) - return None - cmds.select(cl=True) - return ik_ctrls - -def get_ik_handle(pole_vector): # ----- NOT DONE, ADD MORE CONDITIONALS - #FROM THE POLE VECTOR, WE DERIVE THE SELECTION OF THE PARENT AND MIDDLE JOINT THAT THE IK HANDLE INFLUENCES, AND STORE THEM IN VARIABLES - cmds.select(pole_vector, hi=True) - pole_vector_hierarchy = cmds.ls(sl=True) - - for obj in pole_vector_hierarchy: - poleVectorConstraint = cmds.listConnections(obj, type = "poleVectorConstraint") - ik_handle = cmds.listConnections(poleVectorConstraint, type = "ikHandle") - if ik_handle != None: - break - if ik_handle == None: - keywords = "_".join(pole_vector.split("_")[:3]) - ik_handle = cmds.ls(keywords + "*", type="ikHandle") - if ik_handle == None: - keywords = "_".join(pole_vector.split("_")[:2]) - ik_handle = cmds.ls(keywords + "*", type="ikHandle") - - if ik_handle == None or len(ik_handle) == 0: - assist_message("Couldn't obtain IK handle from the rig. Selection order must be Pole Vector first, then IK control. If that doesn't work, this feature is incompatible with this rig.", 4000) - return ik_handle[0] - -def get_ik_handle_joints(ik_handle): - parent_jnt, middle_jnt = cmds.ikHandle(ik_handle, q=True, jl=True) - return parent_jnt, middle_jnt - -def get_original_controls(): - # SEPARATES THE SELECTED CONTROLS INTO THEIR OWN VARIABLES - ik_ctrls = get_original_ik_controls() - if ik_ctrls is None: - return None - pole_vector, ik_ctrl = ik_ctrls - check_if_setup_exists([pole_vector, ik_ctrl]) - ik_handle = get_ik_handle(pole_vector) - parent_jnt, middle_jnt = get_ik_handle_joints(ik_handle) - - return pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt -################ GET ORIGINAL CONTROLS #################### - -################ CREATE TEMP CONTROLS #################### -def create_fk_controls(*controls): - fk_ctrls = [create_control(control + "_temp_FK_CTRL") for control in controls] - fk_ctrls_grps = [cmds.group(fk_ctrl, n=fk_ctrl + "_GRP") for fk_ctrl in fk_ctrls] - return filter_list_unpack_brackets(fk_ctrls_grps, fk_ctrls) - -def create_parent_hierarchy_offsets_controls(offsets, controls): - # SHIFTS THE OFFSETS ELEMENTS SO THAT THE SECOND OFFSET MATCHES THE FIRST CONTROL AND WE PROPERLY PARENT THEM - for offset, control in zip((offsets[1:] + offsets[:1])[:-1], controls[:-1]): - cmds.parent(offset, control) - -def create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt): - # CREATE 3 TEMP LOCATORS, ADD A GROUP ON TOP OF THEM AND PARENT THEM TO EACH OTHER - fk_ctrls_grps, fk_ctrls = create_fk_controls(parent_jnt, middle_jnt, ik_ctrl, pole_vector) - temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - match_control_scale(ik_ctrl, [temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL]) - - create_parent_hierarchy_offsets_controls(fk_ctrls_grps, fk_ctrls) - cmds.parent(temp_poleVector_CTRL_GRP, temp_parent_FK_CTRL) # THE PREVIOUS FUNCTION DOESN'T PLACE THE TEMP POLE VECTOR UNDER THE RIGHT PARENT, SO HERE WE ADJUST THAT - - # CREATES NAME REFERENCE GROUPS FOR ALL ORIGINAL CONTROLS THAT NEED TO BE BAKED WHEN DELETING THE SETUP, PLACES ALL THE CONTENTS IN A NEW TEMP GROUP - reference_grps = create_name_reference_groups([ik_ctrl, pole_vector, ik_handle], "_temp_FK_Name") - create_temp_grp("_temp_FK_Group", parent_jnt, [temp_parent_FK_CTRL_GRP, reference_grps]) - - return fk_ctrls_grps, fk_ctrls -################ CREATE TEMP CONTROLS #################### - -################# CREATES THE REVERSE CONSTRAINT SETUP ################## - -#SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CO NTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS -def create_reverse_constraint_setup_fk(parent, group, child): - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - - cmds.matchTransform(group, parent, position=True, rotation=True) - temp_constraint = constraint(parent, child, "parent", True) - - cmds.bakeResults(child, t=(timeline_start, timeline_end), simulation=False, sb=bake_interval) - cmds.delete(temp_constraint) - -def reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt): - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - # REVERSE CONSTRAINT FROM THE TEMP CONTROLS TO THE ORIGINALS - parent_constraint = constraint(temp_parent_FK_CTRL, parent_jnt, "rotate") - middle_constraint = constraint(temp_middle_FK_CTRL, middle_jnt, "rotate") - child_constraint = constraint(temp_child_FK_CTRL, ik_ctrl, "parent") - - point_constraint = constraint(parent_jnt, temp_parent_FK_CTRL, "translate") # WHEN YOU ROTATE CLAVICLE, NEW PARENT CONTROL TRANSLATES - parent_constraint_two = constraint(temp_middle_FK_CTRL, temp_poleVector_CTRL, "parent") # MAKES THE NEW POLE VECTOR FOLLOW THE PARENT CONTROL - point_constraint_two = constraint(temp_poleVector_CTRL, pole_vector, "translate") # MAKES THE OG POLE VECTOR FOLLOW THE NEW ONE - - return parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two -################# CREATES THE REVERSE CONSTRAINT SETUP ################## - - -#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) - - cmds.autoKeyframe(state=True) - timeline_start, timeline_end, specific_timeline_mode = get_timeline_range() - check_negative_time_range(timeline_start, timeline_end) - - result = get_original_controls() - if result is None: - return - pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt = result - check_anim_layer([pole_vector, ik_ctrl]) - delete_visibility_keys(pole_vector, ik_ctrl) - - fk_ctrls_grps, fk_ctrls = create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt) - temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - - match_rotation_order(ik_ctrl, temp_parent_FK_CTRL) - match_rotation_order(ik_ctrl, temp_middle_FK_CTRL) - match_rotation_order(ik_ctrl, temp_child_FK_CTRL) - ############################# - DONE - ############################# - - - create_reverse_constraint_setup_fk(parent_jnt, temp_parent_FK_CTRL_GRP, temp_parent_FK_CTRL) - create_reverse_constraint_setup_fk(middle_jnt, temp_middle_FK_CTRL_GRP, temp_middle_FK_CTRL) - create_reverse_constraint_setup_fk(ik_ctrl, temp_child_FK_CTRL_GRP, temp_child_FK_CTRL) - create_reverse_constraint_setup_fk(pole_vector, temp_poleVector_CTRL_GRP, temp_poleVector_CTRL) # THIS LOCATOR LACHES ONTO THE OG POLE VECTOR - SO WE KNOW HOW TO BAKE THE OG POLE VECTOR IN THE END - - parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two = reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt) - - # ISOLATE VISIBILITY # - if specific_timeline_mode: - if influence_visibility: - isolate_visibility([ik_ctrl, pole_vector], 0, 1) - isolate_visibility([temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL], 1, 0) - # ISOLATE CONSTRAINT - isolate_constraint([parent_constraint, middle_constraint], [temp_parent_FK_CTRL, temp_middle_FK_CTRL], [parent_jnt, middle_jnt], "Orient") - isolate_constraint([child_constraint], [temp_child_FK_CTRL], [ik_ctrl], "Parent") - - isolate_constraint([point_constraint], [parent_jnt], [temp_parent_FK_CTRL], "Point") - isolate_constraint([parent_constraint_two], [temp_middle_FK_CTRL], [temp_poleVector_CTRL], "Parent") - isolate_constraint([point_constraint_two], [temp_poleVector_CTRL], [pole_vector], "Point") - divide_influence(ik_handle + ".ikBlend", 0, 1) - else: - cmds.setAttr(ik_handle + ".ikBlend", 0) - if influence_visibility: - for original_control in [ik_ctrl, pole_vector]: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True: - cmds.setAttr(temp_shape_node + ".v", 0) - - #CLEAN-UP - hide_controls(temp_poleVector_CTRL) - cmds.cutKey(fk_ctrls, t=(timeline_start, timeline_end), at="translate", option="keys") - hide_attributes("translate", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL) - hide_attributes("scale", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL) - set_control_color(fk_ctrls, 17) - set_control_thickness(fk_ctrls, 2) - - #cmds.keyTangent(fk_ctrls, e=True, itt="auto", ott="auto") - - apply_euler_filter(fk_ctrls) - if clear_keys: - remove_unnecessary_keys(fk_ctrls, ["rotateX", "rotateY", "rotateZ"]) - - cmds.select(ik_handle) - -############################################################################################################################################################################## -########################################################################################################################################################################################################### -############################################################################################################################################################################## - -def get_temporary_setup_selection(): - temp_Selection = cmds.ls(sl=True) - if len(temp_Selection) == 0: - assist_message("To delete a temporary setup, you have to select one of its controls.", 2500) - return temp_Selection - -def check_if_correct_selection(temp_Selection): - if "FK_CTRL" in temp_Selection or "ik_ctrl" in temp_Selection or "pole_vector_ctrl" in temp_Selection: - pass - else: - assist_message("Incorrect selection: " + temp_Selection + " is not a part of any IK FK setup. To delete a setup, select one of its controls and then click the button.", 4500) - -def get_group_selection(selection): - cmds.select(selection) - for i in range(6): - cmds.pickWalk(d="up") - temp_Group = cmds.ls(sl=True)[0] - return temp_Group - -def delete_visibility_keys_after_bake(original_control): - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys") - cmds.setKeyframe(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", v=1) - - -def clean_up(i, attributes, group_Contents): - global timeline_start, timeline_end - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - cmds.autoKeyframe(state=True) - - if group_Contents[i].split("_")[-3:-2][0] == "ISR": - timeline_start, timeline_end = group_Contents[i].split("_")[-2:] - else: - timeline_start, timeline_end = get_timeline_start_end() - - original_control = "_".join(group_Contents[i].split("_")[:-6]) - if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME - original_control = original_control.replace("_dash_", "|") - cmds.bakeResults(original_control, t=(timeline_start, timeline_end), at=attributes, pok=True, sb=bake_interval) - delete_visibility_keys_after_bake(original_control) - - apply_euler_filter([original_control]) - - # #SETS VISIBILITY BACK - # temp_shape_node = cmds.listRelatives(original_control, shapes=True, children=True)[0] - # cmds.setAttr(temp_shape_node + ".v", 1) - - #YOU STORE THE TANGENT TYPE BEFORE THE BAKE, SO THAT INCASE IT WAS IN STEPPED, WE MAKE THE TANGENT STEPPED AGAIN CUZ OTHERWISE IT MESSES UP THE ELBOW AFTERWARDS - return original_control - -def delete_ik_blend(i, group_Contents): - original_control = "_".join(group_Contents[i].split("_")[:-6]) - if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME - original_control = original_control.replace("_dash_", "|") - cmds.cutKey(original_control, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="ikBlend", option="keys") - cmds.setAttr(original_control + ".ikBlend", 1) - return original_control - - -def delete_setup(): - try: - _delete_setup_impl() - except Exception as e: - # Catch all errors to avoid SystemExit - if "SystemExit" not in str(type(e)): - cmds.warning("Delete setup failed: " + str(e)) # Using string concatenation for Python 2.7 compatibility - -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) - temp_group = get_group_selection(selection) - group_contents = cmds.listRelatives(temp_group) - - if "temp_IK_Group" in temp_group: - original_controls = [clean_up(i, ["rotate"], group_contents) for i in range(3,6)] - if clear_keys: - remove_unnecessary_keys(original_controls, ["rotateX", "rotateY", "rotateZ"]) - # remove_unnecessary_keys([original_controls[0], original_controls[2]], ["rotate"]) - # remove_unnecessary_keys([original_controls[1]], ["rotateX"]) - - - elif "temp_FK_Group" in temp_group: - ik_ctrl, pole_vector_ctrl = [clean_up(i, ["translate", "rotate"], group_contents) for i in range(1,3)] - if clear_keys: - remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"]) - remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"]) - ik_handle = delete_ik_blend(3, group_contents) - - - #cmds.lockNode(temp_group, l=False) - if cmds.objExists(temp_group): - cmds.delete(temp_group) - -############################################################################################################################################################################## -########################################################################################################################################################################################################### -############################################################################################################################################################################## - - -#UI LOGIC -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=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=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=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) - - - cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", l="Reduce Keys: ", ncb=1, l1="", cw=(1, 79.5), w=151, vr=False, parent="ikfk_form_layout", - ann="When the animation bakes across, this feature reduces the amount of keyframes on your curves.") - set_form_layout_coordinates("ikfk_form_layout", "ApplyKeyReducer_CheckBox", 105, 5) - - cmds.checkBoxGrp("remove_unnecessary_keys", l="Remove Keys With Same Values: ", ncb=1, l1="", cw = (1, 170), w = 190, vr=False, v1=False, parent ="ikfk_form_layout", - ann="When ticked on, after the bake, static channels get removed.\nStatic channels are curves like scaleX/Y/Z that get baked but have no changes to them across the timeparent_to_child_vector, so they're redundant and take up space.") - set_form_layout_coordinates("ikfk_form_layout", "remove_unnecessary_keys", 133, 11) - - # cmds.floatSliderGrp("remove_keys_threshold", l="Threshold: ", f=True, v=1, cw=(1, 60), w=240, min=1, max=500000, - # parent="ikfk_form_layout", - # ann="The higher the amount, the less keyframes you'll have when applying the key reducer,\nbut you lose out on how precisely the animation gets baked across.") - # set_form_layout_coordinates("remove_keys_threshold", 210, 30) - - cmds.checkBoxGrp("influence_visibility", l="Hide Original Controls: ", ncb=1, l1="", cw=(1, 128), w=166, - vr=False, parent="ikfk_form_layout", v1=True, - ann="When ticked on, after the bake, static channels get removed.\nStatic channels are curves like scaleX/Y/Z that get baked but have no changes to them across the timeparent_to_child_vector, so they're redundant and take up space.") - set_form_layout_coordinates("ikfk_form_layout", "influence_visibility", 160, 9) - - - cmds.floatFieldGrp("Intensity_FloatField", l="Intensity: ", numberOfFields=1, v1=1.0, cw = (1, 52), w = 137, parent ="ikfk_form_layout", - ann="The higher the amount, the less keyframes you'll have when applying the key reducer,\nbut you lose out on how precisely the animation gets baked across.") - set_form_layout_coordinates("ikfk_form_layout", "Intensity_FloatField", 102, 110) - - cmds.intFieldGrp("BakeInterval_IntField", l="Bake Interval: ", numberOfFields=1, v1=1, cw=(1, 85.0), w=198, parent="ikfk_form_layout") - set_form_layout_coordinates("ikfk_form_layout", "BakeInterval_IntField", 75, 1) - - 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=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=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) - # set_form_layout_coordinates("ikfk_form_layout", "Proxy_HRSeparator", 63, -8) - - cmds.showWindow("IK_FK_Switcher") - - -def generateCode(): - # YOU SELECT ONE OF THREE OPTIONS FOR WHAT CODE YOU WANT TO ISOLATE FROM THE SCRIPT: FK TO IK, IK TO FK, DELETE SETUP. - - cmds.scrollField("GenerateCodeOutputWindow", e=True, cl=True) - - code = """ -import maya.cmds as cmds -import maya.mel as mel -import maya.OpenMaya as om - -# from sys import exit # Removed to avoid SystemExit error - -bake_interval = """ + str(cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)) + """ -apply_key_reducer = """ + str(cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True)) + """ -key_reducer_intensity = """ + str(cmds.floatFieldGrp("Intensity_FloatField", q=True, v1=True)) + """ -clear_keys = """ + str(cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)) + """ -influence_visibility = """ + str(cmds.checkBoxGrp("influence_visibility", q=True, v1=True)) + """ - - -################################################################################################################################################################################################################## - -# GENERAL FUNCTIONS -def get_timeline_start_end(): # ---------- DONE - timeline_start = cmds.playbackOptions(min=True, q=True) - timeline_end = cmds.playbackOptions(max=True, q=True) - return timeline_start, timeline_end - -def filter_list_unpack_brackets(*list): # IF LIST HAS 1 ITEM, WE RETURN IT AS ITEM[0] TO REMOVE THE BRACKETS - new_list = [item if len(item) > 1 else item[0] for item in list] - return new_list - -def create_control(name): - points = [(-1, 0, -0), (1, 0, 0), (0, 0, 0), (0, 0, -1), (0, 0, 1), (0, 0, 0), (0, -1, 0), (0, 1, 0)] - temp_control = cmds.curve(n=name, d=1, p=points) - return temp_control - -def apply_euler_filter(controls): - apply_key_reducer = cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True) - key_reducer_intensity = cmds.floatFieldGrp("Intensity_FloatField", q=True, v1=True) - for control in controls: - cmds.select(control) - cmds.filterCurve(control + ".translate", control + ".rotate", f="euler") - if apply_key_reducer: - cmds.filterCurve(control + ".translate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity) - cmds.filterCurve(control + ".rotate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity + 10) - -def hide_attributes(type, *controls): # ---------- DONE - for item in controls: - for attr in ["." + type + "X", "." + type + "Y", "." + type + "Z"]: - cmds.setAttr(item + attr, k=False, l=True, cb=False) - -def hide_controls(*controls): # ---------- DONE - for control in controls: - cmds.setAttr(control + ".v", 0) - -def set_control_color(objects, color): - for obj in objects: - cmds.setAttr(obj + ".overrideEnabled", 1) - cmds.setAttr(obj + ".overrideColor", color) - -def set_control_thickness(objects, value): - for obj in objects: - cmds.setAttr(obj + ".lineWidth", value) - -def set_form_layout_coordinates(form_layout, name, top_coordinates, left_coordinates): # ---------- DONE - #ADJUSTS THE POSITION OF THE UI FEATURES - cmds.formLayout(form_layout, edit=True, attachForm=[(name, "top", top_coordinates), (name, "left", left_coordinates)]) - -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="" + message + "", pos='midCenter', fade=True, fst=time, ck=True) - # Also display warning in Script Editor - cmds.warning(message) - if to_exit: - return # Do not use exit() to avoid SystemExit error - -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 - temp_attribute = [] - if constraint_type == "orient": - temp_attribute = "rotate" - elif constraint_type == "point": - temp_attribute = "translate" - elif constraint_type == "parent": - temp_attribute = ["translate", "rotate"] - return temp_attribute - -def get_locked_attributes(control, attribute): # ---------- OPTIMIZE - #CHECK WHICH ATTRIBUTES ON THE CONTROL ARE LOCKED, SO AS TO KNOW WHICH ONES TO SKIP WHEN APPLYING CONSTRAINTS - if attribute == "parent": - translate_attributes = [".translateX", ".translateY", ".translateZ"] - rotate_attributes = [".rotateX", ".rotateY", ".rotateZ"] - - locked_translate = [attr.lower()[-1:] for attr in translate_attributes if cmds.getAttr(control + attr, lock=True)] - locked_rotate = [attr.lower()[-1:] for attr in rotate_attributes if cmds.getAttr(control + attr, lock=True)] - - locked_attributes = [locked_translate, locked_rotate] - else: - attributes = ["." + attribute + "X", "." + attribute + "Y", "." + attribute + "Z"] - locked_attributes = [attr.lower()[-1:] for attr in attributes if cmds.getAttr(control + attr, lock=True)] - - return locked_attributes - -def constraint(parent, child, type, mo=True): # ---------- DONE - #CONSTRAINT SYSTEM - try: - locked_attributes = get_locked_attributes(child, type) - if type == "parent": - constraint = cmds.parentConstraint(parent, child, maintainOffset = mo, skipTranslate = locked_attributes[0], skipRotate = locked_attributes[1]) - if type == "translate": - constraint = cmds.pointConstraint(parent, child, maintainOffset = mo, skip = locked_attributes) - if type == "rotate": - constraint = cmds.orientConstraint(parent, child, maintainOffset = mo, skip = locked_attributes) - except RuntimeError: - assist_message("Error: Your selected controls are already being influenced by some other source.", 5000) - - - return constraint[0] - -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') - timeline_start, timeline_end = cmds.timeControl(aTimeSlider, q=True, rangeArray=True) - if 1 < (timeline_end - timeline_start): - to_isolate_timeline = True - timeline_end = timeline_end - 1 - else: - to_isolate_timeline = False - timeline_start, timeline_end = get_timeline_start_end() - - return timeline_start, timeline_end, to_isolate_timeline - -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### -def divide_influence(curve, first_value, second_value): - cmds.setKeyframe(curve, t=(timeline_start, timeline_end), value=first_value) - cmds.setKeyframe(curve, t=(timeline_start - 1, timeline_end + 1), value=second_value) - -def get_constraint_blend_index(constraint, control): - # WE'RE TRYING TO FIND THE INDEX AT THE END OF THE CONSTRAINT'S WEIGHT ATTRIBUTE. BECAUSE THERE COULD BE MANY CONSTRAINTS APPLIED ON THE SAME OBJECT, WE CAN'T ALWAYS KNOW WHAT THAT NUMBER WILL BE - for item in cmds.listConnections(constraint, c=True): - if "{0}.{1}W".format(constraint, control) in item: - constraint_index = item[-1:] - blend_index = constraint[-1:] - return constraint_index, blend_index - -def check_if_referenced(original_control, temp_control): - # IF THE RIG IS REFERENCED, WE STORE THE NAME OF THE TEMP LOCATOR WITHOUT THE NAMESPACE, BECAUSE THE CONSTRAINT WE'LL INFLUENCE DON'T HAVE THE NAMESPACE INSIDE - if cmds.referenceQuery(original_control, isNodeReferenced=True) or ":" in original_control: - temp_control = temp_control.split(":")[-1:][0] - return temp_control - -def set_key_frame(constraint_type, control): - temp_attribute = get_constraint_attribute(constraint_type.lower()) - # CHECKS TO SEE IF ORIGINAL CONTROL HAS ANY KEYS ON CURVES ALREADY, IF NOT IT PLACES THEM TO ACTIVATE THE BLEND INDEX - if cmds.keyframe(control, at=temp_attribute, q=True) == None: - cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute) - else: - cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute, i=True) - -def isolate_constraint(constraints, temp_controls, original_controls, constraint_type): - for constraint, temp_control, original_control in zip(constraints, temp_controls, original_controls): - temp_control = check_if_referenced(original_control, temp_control) - - set_key_frame(constraint_type, original_control) - - constraint_index, blend_index = get_constraint_blend_index(constraint, temp_control) - divide_influence(constraint + "." + temp_control + "W" + constraint_index, 1, 0) - divide_influence(original_control + ".blend" + constraint_type + blend_index, 1, 0) - -def isolate_visibility(controls, first_value, second_value): - for control in controls: - temp_shape_nodes = cmds.listRelatives(control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - divide_influence(temp_shape_node + ".v", first_value, second_value) - -def delete_visibility_keys(*original_controls): - if influence_visibility: - for original_control in original_controls: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys") - -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### - -######### CLEAR REDUNDANT KEYS ########## - -def remove_unnecessary_keys(controls, attributes): - tolerance = 0.000002 - for control in controls: - for attr in attributes: - for frame in range(int(timeline_start) + 1, int(timeline_end)): - current_value = cmds.keyframe(control, q=True, t=(frame, frame), at=attr, eval=True)[0] - previous_value = cmds.keyframe(control, q=True, t=(frame - 1, frame - 1), at=attr, eval=True)[0] - #next_value = cmds.keyframe(control, q=True, t=(frame + 1, frame + 1), at=attr, eval=True)[0] - if abs(current_value - previous_value) <= max(1e-09 * max(abs(current_value), abs(previous_value)), tolerance): - cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys") - - # if isclose(current_value, previous_value, abs_tol=tolerance): - # cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys") - - -################# APPLY TO BOTH ############## -def check_if_setup_exists(controls): # DONE - for control in controls: - if cmds.objExists(control + "_temp_IK_Name*") or cmds.objExists(control + "_temp_FK_Name*"): - assist_message("Error: One or more selected controls are part of a pre-existing setup somewhere else on the timeline.", 5000) - -def create_name_reference_groups(contents, suffix): # ---------- DONE - # #CREATES EMPTY GROUPS AS PROXIES FOR REFERENCING THE ACTUAL CONTROLS LATER ON - timeline_mode_keyword = "ISR" if specific_timeline_mode == True else "NIF" - - # BECAUSE SOME NAMES HAVE DASH IN THE MIDDLE IF THEY'RE NOT UNIQUE NAMES, WE HAVE TO REPLACE IT WITH A NEW KEYWORD "_SEPARATOR_" FOR WHEN REFERENCING IT BACK WHEN BAKING. - new_contents = [obj.replace("|", "_dash_") if "|" in obj else obj for obj in contents] - reference_grps = [cmds.group(n=obj + suffix + "_" + timeline_mode_keyword + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end)), em=True) for obj in new_contents] - return reference_grps - -def create_temp_grp(suffix, control_name, controls_to_group): # DONE - temp_group = cmds.group(*controls_to_group, n=control_name + suffix) - #cmds.lockNode(temp_group) - -def match_rotation_order(parent, child): # ---------- DONE - # MATCHES THE ROTATION ORDER FROM THE ORIGINAL SELECTIONS ONTO THE TEMPORARY SETUP CONTROLS - original_rotation_order = cmds.getAttr(parent + ".rotateOrder") - cmds.setAttr(child + ".rotateOrder", original_rotation_order) - -def match_control_scale(parent, children): - # SCALE UP AN OBJECT TO ANOTHER ONE'S BOUNDING BOX SCALE, INCASE IT'S BEEN FREEZE-TRANSFORMED. THIS WAY THE USER DOESN'T HAVE TO MANUALLY ADJUST THE SIZE - children = cmds.ls(children, flatten=True) - if cmds.objectType(parent) == "joint": - parentShapeNode = parent - else: - parentShapeNode = cmds.listRelatives(parent, shapes=True, children=True, pa=True)[0] - - xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(parentShapeNode) - parentDistanceX, parentDistanceY, parentDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin] - - for child in children: - xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(child) - childDistanceX, childDistanceY, childDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin] - - # WE QUERY THE ORIGINAL SCALE OF THE LOCATOR - originalX, originalY, originalZ = cmds.xform(child, q=True, s=True, r=True) - - divisionX, divisionY, divisionZ = [parentDistanceX / childDistanceX, parentDistanceY / childDistanceY, parentDistanceZ / childDistanceZ] - - # WE GET THE FINAL SCALE HERE, WE TAKE THE LONGEST NUMBER AND APPLY THAT TO ALL SCALE AXIS - largestAxis = max([originalX * divisionX, originalY * divisionY, originalZ * divisionZ]) * 3 - newScale = [largestAxis, largestAxis, largestAxis] - if cmds.objectType(parent) == "joint": - if cmds.currentUnit(q=True) == "cm": - cmds.xform(child, scale=(40, 40, 40)) - else: - cmds.xform(child, scale=(0.7, 0.7, 0.7)) - else: - cmds.xform(child, scale=newScale) - - -def check_anim_layer(controls): - root_layer = cmds.animLayer(q=True, r=True) - if root_layer: - layer_list = cmds.animLayer(root_layer, q=True, c=True) - if layer_list != None: - for layer in layer_list: - attribute_list = cmds.animLayer(layer, q=True, attribute=True) - if attribute_list != None: - for attr in attribute_list: - for obj in controls: - if obj in attr: - assist_message("Warning: A control you've selected is in an anim layer. This may mess up the baking process and give bad results", 6000, False) -""" - - if cmds.radioButtonGrp("GenerateCodeOptions_RadioB", q=True, select=True) == 1: - code += """ -############# CREATE IK CONTROLS ################# -def get_original_fk_controls(): # ---------- DONE - fk_ctrls = cmds.ls(sl=True) - if len(fk_ctrls) != 3: - assist_message("Incorrect number of controls selected. To apply an IK setup, you need to select 3 FK controls, in order of parent to child.", 4000) - return None - cmds.select(cl=True) - return fk_ctrls - -def create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl): # ---------- DONE - parent_jnt = cmds.joint(n=parent_ctrl + "_temp_jnt") - middle_jnt = cmds.joint(n=middle_ctrl + "_temp_jnt") - child_jnt = cmds.joint(n=child_ctrl + "_temp_jnt") - - return parent_jnt, middle_jnt, child_jnt - -def create_ik_controls(middle_ctrl, child_ctrl): # ---------- DONE - ik_ctrl = create_control(child_ctrl + "_ik_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - pole_vector_ctrl = create_control(middle_ctrl + "_pole_vector_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - match_control_scale(child_ctrl, [ik_ctrl, pole_vector_ctrl]) - - return ik_ctrl, pole_vector_ctrl - - -def create_ik_handle(parent_jnt, child_jnt, ik_ctrl): # ---------- DONE - # SETS PREFERRED ANGLE ON THE TEMP JOINT CHAIN, APPLIES AN IK HANDLE ON IT - cmds.joint(parent_jnt, e=True, spa=True, ch=True) - temp_IK_Handle = cmds.ikHandle(n=ik_ctrl + "_ikHandle1", sj=parent_jnt, ee=child_jnt)[0] - cmds.matchTransform(temp_IK_Handle, child_jnt) - return temp_IK_Handle - -def create_temp_ik_controls(): # DONE - # STORES ORIGINAL SELECTED FK CONTROLS INTO VARIABLES """ - if len(cmds.ls(sl=True)) == 3: - fk_ctrls = cmds.ls(sl=True) - code += """ - parent_ctrl = """ "\"" + fk_ctrls[0] + "\"" """ - middle_ctrl = """ "\"" + fk_ctrls[1] + "\"" """ - child_ctrl = """ "\"" + fk_ctrls[2] + "\"" """ - fk_ctrls = parent_ctrl, middle_ctrl, child_ctrl - cmds.select(cl=True) - """ - elif len(cmds.ls(sl=True)) == 0: - code += """ - parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls = get_original_fk_controls() """ - else: - assist_message( - "Incorrect number of controls selected. For a specific IK setup, select 3 FK controls. For a generic setup, have no selections.", - 5000) - - code += """ - check_if_setup_exists([parent_ctrl, middle_ctrl, child_ctrl]) - check_anim_layer(fk_ctrls) - - delete_visibility_keys(parent_ctrl, middle_ctrl, child_ctrl) - # CREATES TEMPORARY IK JOINTS AND CONTROLS - parent_jnt, middle_jnt, child_jnt = ik_jnts = create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl) - ik_ctrl, pole_vector_ctrl = ik_ctrls = create_ik_controls(middle_ctrl, child_ctrl) - - # CREATES EMPTY GROUPS FOR REFERENCING THE ORIGINAL CONTROLS WHEN BAKING/DELETING SETUPS - ik_reference_grps = create_name_reference_groups([parent_ctrl, middle_ctrl, child_ctrl], "_temp_IK_Name") - - # STORES WHOLE SETUP IN GROUP - create_temp_grp("_temp_IK_Group", parent_ctrl, [parent_jnt, ik_ctrl, pole_vector_ctrl, ik_reference_grps]) - - return fk_ctrls, ik_jnts, ik_ctrls -############# CREATE IK CONTROLS ################# - - - -############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ################## - -# SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CONTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS -def create_reverse_constraint_setup_ik(parent, child): - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - match_rotation_order(parent, child) - - # POSITIONS THE NEW CONTROLS - if "pole_vector_ctrl" not in child: ################## DON'T LIKE HOW I HAVE TO ADD THESE EXTRA CONDITIONALS IN HERE ##################### - cmds.matchTransform(child, parent, position=True, rotation=True) - - if "jnt" in child: - cmds.makeIdentity(child, apply=True, t=True, r=True, s=True) - - # CONSTRAINTS ORIGINAL CONTROLS TO TEMPORARY, BAKES THE TEMP TO INHERIT ANIM DATA, DELETES CONSTRAINT, CONSTRAINTS TEMPORARY TO ORIGINAL - temp_constraint = constraint(parent, child, "parent") - cmds.bakeResults(child, t=(timeline_start, timeline_end), sb=bake_interval) - cmds.delete(temp_constraint) - - if "pole_vector_ctrl" not in child: - temp_constraint = constraint(child, parent, "rotate") - - return temp_constraint - -def get_vector(position): # ---------- DONE - return om.MVector(position[0], position[1], position[2]) - -def get_pole_vector_position(parent_jnt, middle_jnt, child_jnt): # ---------- DONE - vector_parent = get_vector(cmds.xform(parent_jnt, q=True, t=True, ws=True)) - vector_middle = get_vector(cmds.xform(middle_jnt, q=True, t=True, ws=True)) - vector_child = get_vector(cmds.xform(child_jnt, q=True, t=True, ws=True)) - - parent_to_child_vector = (vector_child - vector_parent) - parent_to_middle_vector = (vector_middle - vector_parent) - - scale_value = (parent_to_child_vector * parent_to_middle_vector) / (parent_to_child_vector * parent_to_child_vector) - parent_to_child_middle_point = parent_to_child_vector * scale_value + vector_parent - - parent_to_middle_length = (vector_middle - vector_parent).length() - middle_to_child_length = (vector_child - vector_middle).length() - total_length = parent_to_middle_length + middle_to_child_length - - pole_vector_position = (vector_middle - parent_to_child_middle_point).normal() * total_length + vector_middle - - return pole_vector_position - -def set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl): - for frame in range(int(timeline_start), int(timeline_end) + 1): - cmds.currentTime(int(frame)) - pole_vector_position = get_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl) - cmds.move(pole_vector_position.x, pole_vector_position.y, pole_vector_position.z, pole_vector_ctrl) - cmds.setKeyframe(pole_vector_ctrl, time=(frame, frame), at="translate") - - -def set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls): - parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls - ik_ctrl, pole_vector_ctrl = ik_ctrls - parent_jnt, middle_jnt, child_jnt = ik_jnts - - # JOINTS - temp_constraint = [create_reverse_constraint_setup_ik(parent, child) for parent, child in zip(fk_ctrls, ik_jnts)] - - # IK CTRL - ik_orient_constraint = create_reverse_constraint_setup_ik(child_jnt, ik_ctrl) - - # POLE-VECTOR - set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl) - #create_reverse_constraint_setup_ik(middle_jnt, pole_vector_ctrl) - - return temp_constraint, ik_orient_constraint -############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ################## - - -#CREATES A TEMPORARY IK SET-UP BY SELECTING EXISTING FK CONTROLS -def fk_to_ik(): - 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) - cmds.autoKeyframe(state=True) - - timeline_start, timeline_end, specific_timeline_mode = get_timeline_range() - check_negative_time_range(timeline_start, timeline_end) - ###### CREATE CONTROLS ###### - result = create_temp_ik_controls() - if result is None: - return - [parent_ctrl, middle_ctrl, child_ctrl], [parent_jnt, middle_jnt, child_jnt], [ik_ctrl, pole_vector_ctrl] = fk_ctrls, ik_jnts, ik_ctrls = result - ###### REPOSITION CONTROLS ###### - temp_constraints, ik_orient_constraint = set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls) - - # CREATES IK HANDLE, ADDS POLE VECTOR CONSTRAINTz - cmds.currentTime(timeline_start) - temp_ik_handle = create_ik_handle(parent_jnt, child_jnt, ik_ctrl) - cmds.parent(temp_ik_handle, ik_ctrl, s=True) - pole_vector_constraint = cmds.poleVectorConstraint(pole_vector_ctrl, temp_ik_handle) - - # MAKES THE IK SETUP WORLD-SPACE - WHEN SHOULDER ROTATES THE IK TRANSLATES BUT DOESN'T ROTATE - point_constraint = constraint(parent_ctrl, parent_jnt, "translate") - - ####### ISOLATE VISIBILITY ###### - if specific_timeline_mode: - if influence_visibility: - isolate_visibility([parent_ctrl, middle_ctrl, child_ctrl], 0, 1) - isolate_visibility([ik_ctrl, pole_vector_ctrl], 1, 0) - ####### ISOLATE CONSTRAINT ###### - isolate_constraint(temp_constraints, ik_jnts, fk_ctrls, "Orient") - else: - if influence_visibility: - for original_control in fk_ctrls: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True: - cmds.setAttr(temp_shape_node + ".v", 0) - - - #isolate_constraint([ik_orient_constraint], [ik_ctrl], [child_jnt], "Orient") - #isolate_constraint([point_constraint], [parent_ctrl], [parent_jnt], "Point") - - ############################# - DONE - ############################# - - #CLEAN-UP # - COULD MAKE INTO ITS OWN FUNCTION - hide_controls(parent_jnt, temp_ik_handle) - cmds.cutKey(pole_vector_ctrl, t=(timeline_start, timeline_end), at="rotate", option="keys") - hide_attributes("rotate", pole_vector_ctrl) - hide_attributes("scale", ik_ctrl, pole_vector_ctrl) - set_control_color(ik_ctrls, 18) - set_control_thickness(ik_ctrls, 2) - #set_control_thickness([ik_ctrl, pole_vector_ctrl], 2) - #cmds.keyTangent(ik_ctrls, e=True, itt="auto", ott="auto") - apply_euler_filter(ik_ctrls) - if clear_keys: - remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"]) - remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"]) - - cmds.select(ik_ctrl) - -fk_to_ik() - """ - elif cmds.radioButtonGrp("GenerateCodeOptions_RadioB", q=True, select=True) == 2: - code += """ - -################ GET ORIGINAL CONTROLS #################### -def get_original_ik_controls(): - ik_ctrls = cmds.ls(sl=True) - if len(ik_ctrls) != 2: - assist_message("Incorrect number of controls selected. To apply an FK setup, you need to select the Pole Vector first and then the IK Control, in order.", 4000) - return None - cmds.select(cl=True) - return ik_ctrls - -def get_ik_handle(pole_vector): # ----- NOT DONE, ADD MORE CONDITIONALS - #FROM THE POLE VECTOR, WE DERIVE THE SELECTION OF THE PARENT AND MIDDLE JOINT THAT THE IK HANDLE INFLUENCES, AND STORE THEM IN VARIABLES - cmds.select(pole_vector, hi=True) - pole_vector_hierarchy = cmds.ls(sl=True) - - for obj in pole_vector_hierarchy: - poleVectorConstraint = cmds.listConnections(obj, type = "poleVectorConstraint") - ik_handle = cmds.listConnections(poleVectorConstraint, type = "ikHandle") - if ik_handle != None: - break - if ik_handle == None: - keywords = "_".join(pole_vector.split("_")[:3]) - ik_handle = cmds.ls(keywords + "*", type="ikHandle") - if ik_handle == None: - keywords = "_".join(pole_vector.split("_")[:2]) - ik_handle = cmds.ls(keywords + "*", type="ikHandle") - - if ik_handle == None or len(ik_handle) == 0: - assist_message("Couldn't obtain IK handle from the rig. Selection order must be Pole Vector first, then IK control. If that doesn't work, this feature is incompatible with this rig.", 4000) - return ik_handle[0] - -def get_ik_handle_joints(ik_handle): - parent_jnt, middle_jnt = cmds.ikHandle(ik_handle, q=True, jl=True) - return parent_jnt, middle_jnt - -def get_original_controls(): - # SEPARATES THE SELECTED CONTROLS INTO THEIR OWN VARIABLES - """ - if len(cmds.ls(sl=True)) == 2: - ik_ctrls = cmds.ls(sl=True) - code += """ - pole_vector = """ "\"" + ik_ctrls[0] + "\"" """ - ik_ctrl = """ "\"" + ik_ctrls[1] + "\"" """ - cmds.select(cl=True) - """ - elif len(cmds.ls(sl=True)) == 0: - code += """ - pole_vector, ik_ctrl = get_original_ik_controls() - """ - else: - assist_message( - "Incorrect number of controls selected. For a specific FK setup, select the Pole Vector and IK Control in order. For a generic setup, have no selections.", - 5000) - code += """ - check_if_setup_exists([pole_vector, ik_ctrl]) - ik_handle = get_ik_handle(pole_vector) - parent_jnt, middle_jnt = get_ik_handle_joints(ik_handle) - - return pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt -################ GET ORIGINAL CONTROLS #################### - -################ CREATE TEMP CONTROLS #################### -def create_fk_controls(*controls): - fk_ctrls = [create_control(control + "_temp_FK_CTRL") for control in controls] - fk_ctrls_grps = [cmds.group(fk_ctrl, n=fk_ctrl + "_GRP") for fk_ctrl in fk_ctrls] - return filter_list_unpack_brackets(fk_ctrls_grps, fk_ctrls) - -def create_parent_hierarchy_offsets_controls(offsets, controls): - # SHIFTS THE OFFSETS ELEMENTS SO THAT THE SECOND OFFSET MATCHES THE FIRST CONTROL AND WE PROPERLY PARENT THEM - for offset, control in zip((offsets[1:] + offsets[:1])[:-1], controls[:-1]): - cmds.parent(offset, control) - -def create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt): - # CREATE 3 TEMP LOCATORS, ADD A GROUP ON TOP OF THEM AND PARENT THEM TO EACH OTHER - fk_ctrls_grps, fk_ctrls = create_fk_controls(parent_jnt, middle_jnt, ik_ctrl, pole_vector) - temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - match_control_scale(ik_ctrl, [temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL]) - - create_parent_hierarchy_offsets_controls(fk_ctrls_grps, fk_ctrls) - cmds.parent(temp_poleVector_CTRL_GRP, temp_parent_FK_CTRL) # THE PREVIOUS FUNCTION DOESN'T PLACE THE TEMP POLE VECTOR UNDER THE RIGHT PARENT, SO HERE WE ADJUST THAT - - # CREATES NAME REFERENCE GROUPS FOR ALL ORIGINAL CONTROLS THAT NEED TO BE BAKED WHEN DELETING THE SETUP, PLACES ALL THE CONTENTS IN A NEW TEMP GROUP - reference_grps = create_name_reference_groups([ik_ctrl, pole_vector, ik_handle], "_temp_FK_Name") - create_temp_grp("_temp_FK_Group", parent_jnt, [temp_parent_FK_CTRL_GRP, reference_grps]) - - return fk_ctrls_grps, fk_ctrls -################ CREATE TEMP CONTROLS #################### - -################# CREATES THE REVERSE CONSTRAINT SETUP ################## - -#SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CO NTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS -def create_reverse_constraint_setup_fk(parent, group, child): - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - - cmds.matchTransform(group, parent, position=True, rotation=True) - temp_constraint = constraint(parent, child, "parent", True) - - cmds.bakeResults(child, t=(timeline_start, timeline_end), simulation=False, sb=bake_interval) - cmds.delete(temp_constraint) - -def reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt): - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - # REVERSE CONSTRAINT FROM THE TEMP CONTROLS TO THE ORIGINALS - parent_constraint = constraint(temp_parent_FK_CTRL, parent_jnt, "rotate") - middle_constraint = constraint(temp_middle_FK_CTRL, middle_jnt, "rotate") - child_constraint = constraint(temp_child_FK_CTRL, ik_ctrl, "parent") - - point_constraint = constraint(parent_jnt, temp_parent_FK_CTRL, "translate") # WHEN YOU ROTATE CLAVICLE, NEW PARENT CONTROL TRANSLATES - parent_constraint_two = constraint(temp_middle_FK_CTRL, temp_poleVector_CTRL, "parent") # MAKES THE NEW POLE VECTOR FOLLOW THE PARENT CONTROL - point_constraint_two = constraint(temp_poleVector_CTRL, pole_vector, "translate") # MAKES THE OG POLE VECTOR FOLLOW THE NEW ONE - - return parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two -################# CREATES THE REVERSE CONSTRAINT SETUP ################## - - -#CREATES A TEMPORARY FK SET-UP BY SELECTING EXISTING IK CONTROLS -def ik_to_fk(): - 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) - - cmds.autoKeyframe(state=True) - timeline_start, timeline_end, specific_timeline_mode = get_timeline_range() - check_negative_time_range(timeline_start, timeline_end) - - result = get_original_controls() - if result is None: - return - pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt = result - check_anim_layer([pole_vector, ik_ctrl]) - delete_visibility_keys(pole_vector, ik_ctrl) - - fk_ctrls_grps, fk_ctrls = create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt) - temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - - match_rotation_order(ik_ctrl, temp_parent_FK_CTRL) - match_rotation_order(ik_ctrl, temp_middle_FK_CTRL) - match_rotation_order(ik_ctrl, temp_child_FK_CTRL) - ############################# - DONE - ############################# - - - create_reverse_constraint_setup_fk(parent_jnt, temp_parent_FK_CTRL_GRP, temp_parent_FK_CTRL) - create_reverse_constraint_setup_fk(middle_jnt, temp_middle_FK_CTRL_GRP, temp_middle_FK_CTRL) - create_reverse_constraint_setup_fk(ik_ctrl, temp_child_FK_CTRL_GRP, temp_child_FK_CTRL) - create_reverse_constraint_setup_fk(pole_vector, temp_poleVector_CTRL_GRP, temp_poleVector_CTRL) # THIS LOCATOR LACHES ONTO THE OG POLE VECTOR - SO WE KNOW HOW TO BAKE THE OG POLE VECTOR IN THE END - - parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two = reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt) - - # ISOLATE VISIBILITY # - if specific_timeline_mode: - if influence_visibility: - isolate_visibility([ik_ctrl, pole_vector], 0, 1) - isolate_visibility([temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL], 1, 0) - # ISOLATE CONSTRAINT - isolate_constraint([parent_constraint, middle_constraint], [temp_parent_FK_CTRL, temp_middle_FK_CTRL], [parent_jnt, middle_jnt], "Orient") - isolate_constraint([child_constraint], [temp_child_FK_CTRL], [ik_ctrl], "Parent") - - isolate_constraint([point_constraint], [parent_jnt], [temp_parent_FK_CTRL], "Point") - isolate_constraint([parent_constraint_two], [temp_middle_FK_CTRL], [temp_poleVector_CTRL], "Parent") - isolate_constraint([point_constraint_two], [temp_poleVector_CTRL], [pole_vector], "Point") - divide_influence(ik_handle + ".ikBlend", 0, 1) - else: - cmds.setAttr(ik_handle + ".ikBlend", 0) - if influence_visibility: - for original_control in [ik_ctrl, pole_vector]: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True: - cmds.setAttr(temp_shape_node + ".v", 0) - - #CLEAN-UP - hide_controls(temp_poleVector_CTRL) - cmds.cutKey(fk_ctrls, t=(timeline_start, timeline_end), at="translate", option="keys") - hide_attributes("translate", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL) - hide_attributes("scale", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL) - set_control_color(fk_ctrls, 17) - set_control_thickness(fk_ctrls, 2) - - #cmds.keyTangent(fk_ctrls, e=True, itt="auto", ott="auto") - - apply_euler_filter(fk_ctrls) - if clear_keys: - remove_unnecessary_keys(fk_ctrls, ["rotateX", "rotateY", "rotateZ"]) - - cmds.select(temp_parent_FK_CTRL) - -ik_to_fk() - -""" - elif cmds.radioButtonGrp("GenerateCodeOptions_RadioB", q=True, select=True) == 3: - code += """ - -def get_temporary_setup_selection(): - temp_Selection = cmds.ls(sl=True) - if len(temp_Selection) == 0: - assist_message("To delete a temporary setup, you have to select one of its controls.", 2500) - return temp_Selection - -def check_if_correct_selection(temp_Selection): - if "FK_CTRL" in temp_Selection or "ik_ctrl" in temp_Selection or "pole_vector_ctrl" in temp_Selection: - pass - else: - assist_message("Incorrect selection: " + temp_Selection + " is not a part of any IK FK setup. To delete a setup, select one of its controls and then click the button.", 4500) - -def get_group_selection(selection): - cmds.select(selection) - for i in range(6): - cmds.pickWalk(d="up") - temp_Group = cmds.ls(sl=True)[0] - return temp_Group - -def delete_visibility_keys_after_bake(original_control): - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys") - cmds.setKeyframe(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", v=1) - - -def clean_up(i, attributes, group_Contents): - global timeline_start, timeline_end - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - cmds.autoKeyframe(state=True) - - if group_Contents[i].split("_")[-3:-2][0] == "ISR": - timeline_start, timeline_end = group_Contents[i].split("_")[-2:] - else: - timeline_start, timeline_end = get_timeline_start_end() - - original_control = "_".join(group_Contents[i].split("_")[:-6]) - if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME - original_control = original_control.replace("_dash_", "|") - cmds.bakeResults(original_control, t=(timeline_start, timeline_end), at=attributes, pok=True, sb=bake_interval) - delete_visibility_keys_after_bake(original_control) - - apply_euler_filter([original_control]) - - # #SETS VISIBILITY BACK - # temp_shape_node = cmds.listRelatives(original_control, shapes=True, children=True)[0] - # cmds.setAttr(temp_shape_node + ".v", 1) - - #YOU STORE THE TANGENT TYPE BEFORE THE BAKE, SO THAT INCASE IT WAS IN STEPPED, WE MAKE THE TANGENT STEPPED AGAIN CUZ OTHERWISE IT MESSES UP THE ELBOW AFTERWARDS - return original_control - -def delete_ik_blend(i, group_Contents): - original_control = "_".join(group_Contents[i].split("_")[:-6]) - if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME - original_control = original_control.replace("_dash_", "|") - cmds.cutKey(original_control, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="ikBlend", option="keys") - cmds.setAttr(original_control + ".ikBlend", 1) - return original_control - - -def delete_setup(): - #BAKES THE PREVIOUS CONTROLS, CLEANS UP THE CURVES, DELETES CURRENT CONTROLS AND BRINGS BACK ORIGINALS - """ - selected_controls = cmds.ls(sl=True) - if len(selected_controls) > 0: - for i in range(6): - cmds.pickWalk(d="up") - for obj in cmds.ls(sl=True): - if "temp_IK_Group" in obj or "temp_FK_Group" in obj: - pass - else: - assist_message( - "Incorrect selection. To generate the code for deleting an IK or FK setup, select one of its controls and then click the button.", - 4500) - code += """ - temp_selection = """ + str(selected_controls) + """ - """ - - elif len(cmds.ls(sl=True)) == 0: - code += """ - temp_selection = get_temporary_setup_selection()""" - - code += """ - for selection in temp_selection: - if cmds.objExists(selection): - check_if_correct_selection(selection) - temp_group = get_group_selection(selection) - group_contents = cmds.listRelatives(temp_group) - - if "temp_IK_Group" in temp_group: - original_controls = [clean_up(i, ["rotate"], group_contents) for i in range(3,6)] - if clear_keys: - remove_unnecessary_keys(original_controls, ["rotateX", "rotateY", "rotateZ"]) - # remove_unnecessary_keys([original_controls[0], original_controls[2]], ["rotate"]) - # remove_unnecessary_keys([original_controls[1]], ["rotateX"]) - - - elif "temp_FK_Group" in temp_group: - ik_ctrl, pole_vector_ctrl = [clean_up(i, ["translate", "rotate"], group_contents) for i in range(1,3)] - if clear_keys: - remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"]) - remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"]) - ik_handle = delete_ik_blend(3, group_contents) - - - #cmds.lockNode(temp_group, l=False) - if cmds.objExists(temp_group): - cmds.delete(temp_group) - -delete_setup() - -""" - - cmds.scrollField("GenerateCodeOutputWindow", e=True, tx=code) -def extraOptions(): - if cmds.window("Extra_Options", ex=True): - cmds.deleteUI("Extra_Options") - - cmds.window("Extra_Options", title="Extra Options", wh=[344, 242], s=False) - cmds.formLayout("ikfk_form_layout_extra", numberOfDivisions=100, w=343, h=240) - - 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=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" - "Important note: Whatever values you have for the settings in the main window, those values will be stored within the code you generate.") - set_form_layout_coordinates("ikfk_form_layout_extra", "Generate_Code_Button", 17, 16) - - cmds.radioButtonGrp("GenerateCodeOptions_RadioB", vr=True, numberOfRadioButtons=3, en1=True, l1="FK to IK", - l2="IK to FK", l3="Delete Setup", parent="ikfk_form_layout_extra") - cmds.radioButtonGrp("GenerateCodeOptions_RadioB", e=True, select=1) - set_form_layout_coordinates("ikfk_form_layout_extra","GenerateCodeOptions_RadioB", 11.5, 135) - - cmds.scrollField("GenerateCodeOutputWindow", width=312, height=150) - set_form_layout_coordinates("ikfk_form_layout_extra", "GenerateCodeOutputWindow", 75, 16) - - cmds.showWindow("Extra_Options") - - -# Export main functions for external use -__all__ = [ - 'fk_to_ik', - 'ik_to_fk', - 'delete_setup', - 'documentation', - 'extraOptions', - 'user_interface', - 'show', -] - - -def show(): - """ - Convenience function: Launch IK/FK Switcher UI - - Usage: - import animation_tools.ik_fk_switcher as ikfk - ikfk.show() - """ - user_interface() - - -if __name__ == "__main__": - user_interface() \ No newline at end of file diff --git a/2023/scripts/animation_tools/ikfx_switch.py b/2023/scripts/animation_tools/ikfx_switch.py new file mode 100644 index 0000000..95b9ebf --- /dev/null +++ b/2023/scripts/animation_tools/ikfx_switch.py @@ -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() diff --git a/2023/shelves/shelf_Nexus_Animation.mel b/2023/shelves/shelf_Nexus_Animation.mel index 9b095a8..4d00340 100644 --- a/2023/shelves/shelf_Nexus_Animation.mel +++ b/2023/shelves/shelf_Nexus_Animation.mel @@ -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 diff --git a/2025/icons/ik_fk_switcher.png b/2025/icons/ikfk_switch.png similarity index 100% rename from 2025/icons/ik_fk_switcher.png rename to 2025/icons/ikfk_switch.png diff --git a/2025/scripts/animation_tools/IKFK_SWITCH_README.md b/2025/scripts/animation_tools/IKFK_SWITCH_README.md new file mode 100644 index 0000000..2d3160b --- /dev/null +++ b/2025/scripts/animation_tools/IKFK_SWITCH_README.md @@ -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. **属性加载失败** + - 确保在通道盒中选择了属性 + - 检查属性是否可关键帧化 diff --git a/2025/scripts/animation_tools/ik_fk_switcher.py b/2025/scripts/animation_tools/ik_fk_switcher.py deleted file mode 100644 index 2598c7b..0000000 --- a/2025/scripts/animation_tools/ik_fk_switcher.py +++ /dev/null @@ -1,1700 +0,0 @@ -""" -You can use this script for any commercial or non-commercial projects. You're not allowed to sell this script. -Author - Petar3D -Initial Release Date - 10.05.2023 -Version - 1.0 (10.05.2023) -Version - 2.0 (02.11.2023) -Version - 3.0 (12.03.2025) - Current - -Description - Tool that allows you to build a temporary IK/FK setup on any rig, while preserving animation. Example - You select the controls for you arm FK chain (Shoulder, Elbow, Wrist), -and then click the "FK to IK" button to apply an IK setup on top of your original FK controls. You can also isolate the code from the UI so you can put it into a marking menu or on the shelf. -""" - -import maya.cmds as cmds -import maya.mel as mel -import maya.OpenMaya as om -#from math import isclose -# from sys import exit # Removed to avoid SystemExit error - -################################################################################################################################################################################################################## - -# GENERAL FUNCTIONS -def get_timeline_start_end(): # ---------- DONE - timeline_start = cmds.playbackOptions(min=True, q=True) - timeline_end = cmds.playbackOptions(max=True, q=True) - return timeline_start, timeline_end - -def filter_list_unpack_brackets(*list): # IF LIST HAS 1 ITEM, WE RETURN IT AS ITEM[0] TO REMOVE THE BRACKETS - new_list = [item if len(item) > 1 else item[0] for item in list] - return new_list - -def create_control(name): - points = [(-1, 0, -0), (1, 0, 0), (0, 0, 0), (0, 0, -1), (0, 0, 1), (0, 0, 0), (0, -1, 0), (0, 1, 0)] - temp_control = cmds.curve(n=name, d=1, p=points) - return temp_control - -def apply_euler_filter(controls): - apply_key_reducer = cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True) - key_reducer_intensity = cmds.floatFieldGrp("Intensity_FloatField", q=True, v1=True) - for control in controls: - cmds.select(control) - cmds.filterCurve(control + ".translate", control + ".rotate", f="euler") - if apply_key_reducer: - cmds.filterCurve(control + ".translate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity) - cmds.filterCurve(control + ".rotate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity + 10) - -def hide_attributes(type, *controls): # ---------- DONE - for item in controls: - for attr in ["." + type + "X", "." + type + "Y", "." + type + "Z"]: - cmds.setAttr(item + attr, k=False, l=True, cb=False) - -def hide_controls(*controls): # ---------- DONE - for control in controls: - cmds.setAttr(control + ".v", 0) - -def set_control_color(objects, color): - for obj in objects: - cmds.setAttr(obj + ".overrideEnabled", 1) - cmds.setAttr(obj + ".overrideColor", color) - -def set_control_thickness(objects, value): - for obj in objects: - cmds.setAttr(obj + ".lineWidth", value) - -def set_form_layout_coordinates(form_layout, name, top_coordinates, left_coordinates): # ---------- DONE - #ADJUSTS THE POSITION OF THE UI FEATURES - cmds.formLayout(form_layout, edit=True, attachForm=[(name, "top", top_coordinates), (name, "left", left_coordinates)]) - -def documentation(): - cmds.showHelp("https://petarpehchevski3d.gumroad.com/l/ikfkswitcher", a=True) - -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="" + message + "", pos='midCenter', fade=True, fst=time, ck=True) - # Also display warning in Script Editor - cmds.warning(message) - if to_exit: - return # Do not use exit() to avoid SystemExit error - -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 - temp_attribute = [] - if constraint_type == "orient": - temp_attribute = "rotate" - elif constraint_type == "point": - temp_attribute = "translate" - elif constraint_type == "parent": - temp_attribute = ["translate", "rotate"] - return temp_attribute - -def get_locked_attributes(control, attribute): # ---------- OPTIMIZE - #CHECK WHICH ATTRIBUTES ON THE CONTROL ARE LOCKED, SO AS TO KNOW WHICH ONES TO SKIP WHEN APPLYING CONSTRAINTS - if attribute == "parent": - translate_attributes = [".translateX", ".translateY", ".translateZ"] - rotate_attributes = [".rotateX", ".rotateY", ".rotateZ"] - - locked_translate = [attr.lower()[-1:] for attr in translate_attributes if cmds.getAttr(control + attr, lock=True)] - locked_rotate = [attr.lower()[-1:] for attr in rotate_attributes if cmds.getAttr(control + attr, lock=True)] - - locked_attributes = [locked_translate, locked_rotate] - else: - attributes = ["." + attribute + "X", "." + attribute + "Y", "." + attribute + "Z"] - locked_attributes = [attr.lower()[-1:] for attr in attributes if cmds.getAttr(control + attr, lock=True)] - - return locked_attributes - -def constraint(parent, child, type, mo=True): # ---------- DONE - #CONSTRAINT SYSTEM - try: - locked_attributes = get_locked_attributes(child, type) - if type == "parent": - constraint = cmds.parentConstraint(parent, child, maintainOffset = mo, skipTranslate = locked_attributes[0], skipRotate = locked_attributes[1]) - if type == "translate": - constraint = cmds.pointConstraint(parent, child, maintainOffset = mo, skip = locked_attributes) - if type == "rotate": - constraint = cmds.orientConstraint(parent, child, maintainOffset = mo, skip = locked_attributes) - except RuntimeError: - assist_message("Error: Your selected controls are already being influenced by some other source.", 5000) - - return constraint[0] - -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) - -def get_timeline_range(): - aTimeSlider = mel.eval('$tmpVar=$gPlayBackSlider') - timeline_start, timeline_end = cmds.timeControl(aTimeSlider, q=True, rangeArray=True) - if 1 < (timeline_end - timeline_start): - to_isolate_timeline = True - timeline_end = timeline_end - 1 - else: - to_isolate_timeline = False - timeline_start, timeline_end = get_timeline_start_end() - - return timeline_start, timeline_end, to_isolate_timeline - -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### -def divide_influence(curve, first_value, second_value): - cmds.setKeyframe(curve, t=(timeline_start, timeline_end), value=first_value) - cmds.setKeyframe(curve, t=(timeline_start - 1, timeline_end + 1), value=second_value) - -def get_constraint_blend_index(constraint, control): - # WE'RE TRYING TO FIND THE INDEX AT THE END OF THE CONSTRAINT'S WEIGHT ATTRIBUTE. BECAUSE THERE COULD BE MANY CONSTRAINTS APPLIED ON THE SAME OBJECT, WE CAN'T ALWAYS KNOW WHAT THAT NUMBER WILL BE - for item in cmds.listConnections(constraint, c=True): - if "{0}.{1}W".format(constraint, control) in item: - constraint_index = item[-1:] - blend_index = constraint[-1:] - return constraint_index, blend_index - -def check_if_referenced(original_control, temp_control): - # IF THE RIG IS REFERENCED, WE STORE THE NAME OF THE TEMP LOCATOR WITHOUT THE NAMESPACE, BECAUSE THE CONSTRAINT WE'LL INFLUENCE DON'T HAVE THE NAMESPACE INSIDE - if cmds.referenceQuery(original_control, isNodeReferenced=True) or ":" in original_control: - temp_control = temp_control.split(":")[-1:][0] - return temp_control - -def set_key_frame(constraint_type, control): - temp_attribute = get_constraint_attribute(constraint_type.lower()) - # CHECKS TO SEE IF ORIGINAL CONTROL HAS ANY KEYS ON CURVES ALREADY, IF NOT IT PLACES THEM TO ACTIVATE THE BLEND INDEX - if cmds.keyframe(control, at=temp_attribute, q=True) == None: - cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute) - else: - cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute, i=True) - -def isolate_constraint(constraints, temp_controls, original_controls, constraint_type): - for constraint, temp_control, original_control in zip(constraints, temp_controls, original_controls): - temp_control = check_if_referenced(original_control, temp_control) - - set_key_frame(constraint_type, original_control) - - constraint_index, blend_index = get_constraint_blend_index(constraint, temp_control) - divide_influence(constraint + "." + temp_control + "W" + constraint_index, 1, 0) - divide_influence(original_control + ".blend" + constraint_type + blend_index, 1, 0) - -def isolate_visibility(controls, first_value, second_value): - for control in controls: - temp_shape_nodes = cmds.listRelatives(control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - divide_influence(temp_shape_node + ".v", first_value, second_value) - -def delete_visibility_keys(*original_controls): - if influence_visibility: - for original_control in original_controls: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys") - -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### - -######### CLEAR REDUNDANT KEYS ########## - -def remove_unnecessary_keys(controls, attributes): - tolerance = 0.000002 - for control in controls: - for attr in attributes: - for frame in range(int(timeline_start) + 1, int(timeline_end)): - current_value = cmds.keyframe(control, q=True, t=(frame, frame), at=attr, eval=True)[0] - previous_value = cmds.keyframe(control, q=True, t=(frame - 1, frame - 1), at=attr, eval=True)[0] - #next_value = cmds.keyframe(control, q=True, t=(frame + 1, frame + 1), at=attr, eval=True)[0] - if abs(current_value - previous_value) <= max(1e-09 * max(abs(current_value), abs(previous_value)), tolerance): - cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys") - - # if isclose(current_value, previous_value, abs_tol=tolerance): - # cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys") - - -################# APPLY TO BOTH ############## -def check_if_setup_exists(controls): # DONE - for control in controls: - if cmds.objExists(control + "_temp_IK_Name*") or cmds.objExists(control + "_temp_FK_Name*"): - assist_message("Error: One or more selected controls are part of a pre-existing setup somewhere else on the timeline.", 5000) - -def create_name_reference_groups(contents, suffix): # ---------- DONE - # #CREATES EMPTY GROUPS AS PROXIES FOR REFERENCING THE ACTUAL CONTROLS LATER ON - timeline_mode_keyword = "ISR" if specific_timeline_mode == True else "NIF" - - # BECAUSE SOME NAMES HAVE DASH IN THE MIDDLE IF THEY'RE NOT UNIQUE NAMES, WE HAVE TO REPLACE IT WITH A NEW KEYWORD "_SEPARATOR_" FOR WHEN REFERENCING IT BACK WHEN BAKING. - new_contents = [obj.replace("|", "_dash_") if "|" in obj else obj for obj in contents] - reference_grps = [] - # WE HAD TO DO A CHANGE HERE BECAUSE IF YOUR PROJECT HAS NAMESPACES, IF YOU CREATE A GROUP NODE, IT'LL INHERIT FROM ALL THE NAMESPACES. THIS WAS PROBLEMTIC. I NEED THE GROUP NAMES TO BE IDENTICAL TO THE CONTROL NAME. SO I JUST GREATE THE GROUP, AND RENAME - for obj in new_contents: - temp_group = cmds.group(n=obj, em=True) - temp_group = cmds.rename(temp_group, obj + suffix + "_" + timeline_mode_keyword + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - reference_grps.append(temp_group) - return reference_grps - -def create_temp_grp(suffix, control_name, controls_to_group): # DONE - temp_group = cmds.group(*controls_to_group, n=control_name + suffix) - cmds.rename(temp_group, control_name + suffix) - #cmds.lockNode(temp_group) - -def match_rotation_order(parent, child): # ---------- DONE - # MATCHES THE ROTATION ORDER FROM THE ORIGINAL SELECTIONS ONTO THE TEMPORARY SETUP CONTROLS - original_rotation_order = cmds.getAttr(parent + ".rotateOrder") - cmds.setAttr(child + ".rotateOrder", original_rotation_order) - -def match_control_scale(parent, children): - # SCALE UP AN OBJECT TO ANOTHER ONE'S BOUNDING BOX SCALE, INCASE IT'S BEEN FREEZE-TRANSFORMED. THIS WAY THE USER DOESN'T HAVE TO MANUALLY ADJUST THE SIZE - children = cmds.ls(children, flatten=True) - if cmds.objectType(parent) == "joint": - parentShapeNode = parent - else: - parentShapeNode = cmds.listRelatives(parent, shapes=True, children=True, pa=True)[0] - - xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(parentShapeNode) - parentDistanceX, parentDistanceY, parentDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin] - - for child in children: - xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(child) - childDistanceX, childDistanceY, childDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin] - - # WE QUERY THE ORIGINAL SCALE OF THE LOCATOR - originalX, originalY, originalZ = cmds.xform(child, q=True, s=True, r=True) - - divisionX, divisionY, divisionZ = [parentDistanceX / childDistanceX, parentDistanceY / childDistanceY, parentDistanceZ / childDistanceZ] - - # WE GET THE FINAL SCALE HERE, WE TAKE THE LONGEST NUMBER AND APPLY THAT TO ALL SCALE AXIS - largestAxis = max([originalX * divisionX, originalY * divisionY, originalZ * divisionZ]) * 3 - newScale = [largestAxis, largestAxis, largestAxis] - if cmds.objectType(parent) == "joint": - if cmds.currentUnit(q=True) == "cm": - cmds.xform(child, scale=(40, 40, 40)) - else: - cmds.xform(child, scale=(0.7, 0.7, 0.7)) - else: - cmds.xform(child, scale=newScale) - - -def check_anim_layer(controls): - root_layer = cmds.animLayer(q=True, r=True) - if root_layer: - layer_list = cmds.animLayer(root_layer, q=True, c=True) - if layer_list != None: - for layer in layer_list: - attribute_list = cmds.animLayer(layer, q=True, attribute=True) - if attribute_list != None: - for attr in attribute_list: - for obj in controls: - if obj in attr: - assist_message("Warning: A control you've selected is in an anim layer. This may mess up the baking process and give bad results", 6000, False) - -################# APPLY TO BOTH ############## - -############# CREATE IK CONTROLS ################# -def get_original_fk_controls(): # ---------- DONE - fk_ctrls = cmds.ls(sl=True) - if len(fk_ctrls) != 3: - assist_message("Incorrect number of controls selected. To apply an IK setup, you need to select 3 FK controls, in order of parent to child.", 4000) - return None - cmds.select(cl=True) - return fk_ctrls - -def create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl): # ---------- DONE - parent_jnt = cmds.joint(n=parent_ctrl + "_temp_jnt") - middle_jnt = cmds.joint(n=middle_ctrl + "_temp_jnt") - child_jnt = cmds.joint(n=child_ctrl + "_temp_jnt") - - return parent_jnt, middle_jnt, child_jnt - -def create_ik_controls(middle_ctrl, child_ctrl): # ---------- DONE - ik_ctrl = create_control(child_ctrl + "_ik_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - pole_vector_ctrl = create_control(middle_ctrl + "_pole_vector_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - match_control_scale(child_ctrl, [ik_ctrl, pole_vector_ctrl]) - - return ik_ctrl, pole_vector_ctrl - - -def create_ik_handle(parent_jnt, child_jnt, ik_ctrl): # ---------- DONE - # SETS PREFERRED ANGLE ON THE TEMP JOINT CHAIN, APPLIES AN IK HANDLE ON IT - cmds.joint(parent_jnt, e=True, spa=True, ch=True) - temp_IK_Handle = cmds.ikHandle(n=ik_ctrl + "_ikHandle1", sj=parent_jnt, ee=child_jnt)[0] - cmds.matchTransform(temp_IK_Handle, child_jnt) - return temp_IK_Handle - -def create_temp_ik_controls(): # DONE - # STORES ORIGINAL SELECTED FK CONTROLS INTO VARIABLES - fk_ctrls = get_original_fk_controls() - if fk_ctrls is None: - return None - parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls - check_if_setup_exists([parent_ctrl, middle_ctrl, child_ctrl]) - check_anim_layer(fk_ctrls) - - delete_visibility_keys(parent_ctrl, middle_ctrl, child_ctrl) - # CREATES TEMPORARY IK JOINTS AND CONTROLS - parent_jnt, middle_jnt, child_jnt = ik_jnts = create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl) - ik_ctrl, pole_vector_ctrl = ik_ctrls = create_ik_controls(middle_ctrl, child_ctrl) - - # CREATES EMPTY GROUPS FOR REFERENCING THE ORIGINAL CONTROLS WHEN BAKING/DELETING SETUPS - ik_reference_grps = create_name_reference_groups([parent_ctrl, middle_ctrl, child_ctrl], "_temp_IK_Name") - - # STORES WHOLE SETUP IN GROUP - create_temp_grp("_temp_IK_Group", parent_ctrl, [parent_jnt, ik_ctrl, pole_vector_ctrl, ik_reference_grps]) - - return fk_ctrls, ik_jnts, ik_ctrls -############# CREATE IK CONTROLS ################# - - - -############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ################## - -# SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CONTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS -def create_reverse_constraint_setup_ik(parent, child): - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - match_rotation_order(parent, child) - - # POSITIONS THE NEW CONTROLS - if "pole_vector_ctrl" not in child: ################## DON'T LIKE HOW I HAVE TO ADD THESE EXTRA CONDITIONALS IN HERE ##################### - cmds.matchTransform(child, parent, position=True, rotation=True) - - if "jnt" in child: - cmds.makeIdentity(child, apply=True, t=True, r=True, s=True) - - # CONSTRAINTS ORIGINAL CONTROLS TO TEMPORARY, BAKES THE TEMP TO INHERIT ANIM DATA, DELETES CONSTRAINT, CONSTRAINTS TEMPORARY TO ORIGINAL - temp_constraint = constraint(parent, child, "parent") - cmds.bakeResults(child, t=(timeline_start, timeline_end), sb=bake_interval) - cmds.delete(temp_constraint) - - if "pole_vector_ctrl" not in child: - temp_constraint = constraint(child, parent, "rotate") - - return temp_constraint - -def get_vector(position): # ---------- DONE - return om.MVector(position[0], position[1], position[2]) - -def get_pole_vector_position(parent_jnt, middle_jnt, child_jnt): # ---------- DONE - vector_parent = get_vector(cmds.xform(parent_jnt, q=True, t=True, ws=True)) - vector_middle = get_vector(cmds.xform(middle_jnt, q=True, t=True, ws=True)) - vector_child = get_vector(cmds.xform(child_jnt, q=True, t=True, ws=True)) - - parent_to_child_vector = (vector_child - vector_parent) - parent_to_middle_vector = (vector_middle - vector_parent) - - scale_value = (parent_to_child_vector * parent_to_middle_vector) / (parent_to_child_vector * parent_to_child_vector) - parent_to_child_middle_point = parent_to_child_vector * scale_value + vector_parent - - parent_to_middle_length = (vector_middle - vector_parent).length() - middle_to_child_length = (vector_child - vector_middle).length() - total_length = parent_to_middle_length + middle_to_child_length - - pole_vector_position = (vector_middle - parent_to_child_middle_point).normal() * total_length + vector_middle - - return pole_vector_position - -def set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl): - for frame in range(int(timeline_start), int(timeline_end) + 1): - cmds.currentTime(int(frame)) - pole_vector_position = get_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl) - cmds.move(pole_vector_position.x, pole_vector_position.y, pole_vector_position.z, pole_vector_ctrl) - cmds.setKeyframe(pole_vector_ctrl, time=(frame, frame), at="translate") - - -def set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls): - parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls - ik_ctrl, pole_vector_ctrl = ik_ctrls - parent_jnt, middle_jnt, child_jnt = ik_jnts - - # JOINTS - temp_constraint = [create_reverse_constraint_setup_ik(parent, child) for parent, child in zip(fk_ctrls, ik_jnts)] - - # IK CTRL - ik_orient_constraint = create_reverse_constraint_setup_ik(child_jnt, ik_ctrl) - - # POLE-VECTOR - set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl) - #create_reverse_constraint_setup_ik(middle_jnt, pole_vector_ctrl) - - return temp_constraint, ik_orient_constraint -############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ################## - - -#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) - cmds.autoKeyframe(state=True) - - timeline_start, timeline_end, specific_timeline_mode = get_timeline_range() - check_negative_time_range(timeline_start, timeline_end) - ###### CREATE CONTROLS ###### - result = create_temp_ik_controls() - if result is None: - return - [parent_ctrl, middle_ctrl, child_ctrl], [parent_jnt, middle_jnt, child_jnt], [ik_ctrl, pole_vector_ctrl] = fk_ctrls, ik_jnts, ik_ctrls = result - ###### REPOSITION CONTROLS ###### - temp_constraints, ik_orient_constraint = set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls) - - # CREATES IK HANDLE, ADDS POLE VECTOR CONSTRAINTz - cmds.currentTime(timeline_start) - temp_ik_handle = create_ik_handle(parent_jnt, child_jnt, ik_ctrl) - cmds.parent(temp_ik_handle, ik_ctrl, s=True) - pole_vector_constraint = cmds.poleVectorConstraint(pole_vector_ctrl, temp_ik_handle) - - # MAKES THE IK SETUP WORLD-SPACE - WHEN SHOULDER ROTATES THE IK TRANSLATES BUT DOESN'T ROTATE - point_constraint = constraint(parent_ctrl, parent_jnt, "translate") - - ####### ISOLATE VISIBILITY ###### - if specific_timeline_mode: - if influence_visibility: - isolate_visibility([parent_ctrl, middle_ctrl, child_ctrl], 0, 1) - isolate_visibility([ik_ctrl, pole_vector_ctrl], 1, 0) - ####### ISOLATE CONSTRAINT ###### - isolate_constraint(temp_constraints, ik_jnts, fk_ctrls, "Orient") - else: - if influence_visibility: - for original_control in fk_ctrls: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True: - cmds.setAttr(temp_shape_node + ".v", 0) - - - #isolate_constraint([ik_orient_constraint], [ik_ctrl], [child_jnt], "Orient") - #isolate_constraint([point_constraint], [parent_ctrl], [parent_jnt], "Point") - - ############################# - DONE - ############################# - - #CLEAN-UP # - COULD MAKE INTO ITS OWN FUNCTION - hide_controls(parent_jnt, temp_ik_handle) - cmds.cutKey(pole_vector_ctrl, t=(timeline_start, timeline_end), at="rotate", option="keys") - hide_attributes("rotate", pole_vector_ctrl) - hide_attributes("scale", ik_ctrl, pole_vector_ctrl) - set_control_color(ik_ctrls, 18) - set_control_thickness(ik_ctrls, 2) - #set_control_thickness([ik_ctrl, pole_vector_ctrl], 2) - #cmds.keyTangent(ik_ctrls, e=True, itt="auto", ott="auto") - apply_euler_filter(ik_ctrls) - if clear_keys: - remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"]) - remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"]) - - cmds.select(ik_ctrl) - - -################ GET ORIGINAL CONTROLS #################### -def get_original_ik_controls(): - ik_ctrls = cmds.ls(sl=True) - if len(ik_ctrls) != 2: - assist_message("Incorrect number of controls selected. To apply an FK setup, you need to select the Pole Vector first and then the IK Control, in order.", 4000) - return None - cmds.select(cl=True) - return ik_ctrls - -def get_ik_handle(pole_vector): # ----- NOT DONE, ADD MORE CONDITIONALS - #FROM THE POLE VECTOR, WE DERIVE THE SELECTION OF THE PARENT AND MIDDLE JOINT THAT THE IK HANDLE INFLUENCES, AND STORE THEM IN VARIABLES - cmds.select(pole_vector, hi=True) - pole_vector_hierarchy = cmds.ls(sl=True) - - for obj in pole_vector_hierarchy: - poleVectorConstraint = cmds.listConnections(obj, type = "poleVectorConstraint") - ik_handle = cmds.listConnections(poleVectorConstraint, type = "ikHandle") - if ik_handle != None: - break - if ik_handle == None: - keywords = "_".join(pole_vector.split("_")[:3]) - ik_handle = cmds.ls(keywords + "*", type="ikHandle") - if ik_handle == None: - keywords = "_".join(pole_vector.split("_")[:2]) - ik_handle = cmds.ls(keywords + "*", type="ikHandle") - - if ik_handle == None or len(ik_handle) == 0: - assist_message("Couldn't obtain IK handle from the rig. Selection order must be Pole Vector first, then IK control. If that doesn't work, this feature is incompatible with this rig.", 4000) - return ik_handle[0] - -def get_ik_handle_joints(ik_handle): - parent_jnt, middle_jnt = cmds.ikHandle(ik_handle, q=True, jl=True) - return parent_jnt, middle_jnt - -def get_original_controls(): - # SEPARATES THE SELECTED CONTROLS INTO THEIR OWN VARIABLES - ik_ctrls = get_original_ik_controls() - if ik_ctrls is None: - return None - pole_vector, ik_ctrl = ik_ctrls - check_if_setup_exists([pole_vector, ik_ctrl]) - ik_handle = get_ik_handle(pole_vector) - parent_jnt, middle_jnt = get_ik_handle_joints(ik_handle) - - return pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt -################ GET ORIGINAL CONTROLS #################### - -################ CREATE TEMP CONTROLS #################### -def create_fk_controls(*controls): - fk_ctrls = [create_control(control + "_temp_FK_CTRL") for control in controls] - fk_ctrls_grps = [cmds.group(fk_ctrl, n=fk_ctrl + "_GRP") for fk_ctrl in fk_ctrls] - return filter_list_unpack_brackets(fk_ctrls_grps, fk_ctrls) - -def create_parent_hierarchy_offsets_controls(offsets, controls): - # SHIFTS THE OFFSETS ELEMENTS SO THAT THE SECOND OFFSET MATCHES THE FIRST CONTROL AND WE PROPERLY PARENT THEM - for offset, control in zip((offsets[1:] + offsets[:1])[:-1], controls[:-1]): - cmds.parent(offset, control) - -def create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt): - # CREATE 3 TEMP LOCATORS, ADD A GROUP ON TOP OF THEM AND PARENT THEM TO EACH OTHER - fk_ctrls_grps, fk_ctrls = create_fk_controls(parent_jnt, middle_jnt, ik_ctrl, pole_vector) - temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - match_control_scale(ik_ctrl, [temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL]) - - create_parent_hierarchy_offsets_controls(fk_ctrls_grps, fk_ctrls) - cmds.parent(temp_poleVector_CTRL_GRP, temp_parent_FK_CTRL) # THE PREVIOUS FUNCTION DOESN'T PLACE THE TEMP POLE VECTOR UNDER THE RIGHT PARENT, SO HERE WE ADJUST THAT - - # CREATES NAME REFERENCE GROUPS FOR ALL ORIGINAL CONTROLS THAT NEED TO BE BAKED WHEN DELETING THE SETUP, PLACES ALL THE CONTENTS IN A NEW TEMP GROUP - reference_grps = create_name_reference_groups([ik_ctrl, pole_vector, ik_handle], "_temp_FK_Name") - create_temp_grp("_temp_FK_Group", parent_jnt, [temp_parent_FK_CTRL_GRP, reference_grps]) - - return fk_ctrls_grps, fk_ctrls -################ CREATE TEMP CONTROLS #################### - -################# CREATES THE REVERSE CONSTRAINT SETUP ################## - -#SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CO NTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS -def create_reverse_constraint_setup_fk(parent, group, child): - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - - cmds.matchTransform(group, parent, position=True, rotation=True) - temp_constraint = constraint(parent, child, "parent", True) - - cmds.bakeResults(child, t=(timeline_start, timeline_end), simulation=False, sb=bake_interval) - cmds.delete(temp_constraint) - -def reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt): - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - # REVERSE CONSTRAINT FROM THE TEMP CONTROLS TO THE ORIGINALS - parent_constraint = constraint(temp_parent_FK_CTRL, parent_jnt, "rotate") - middle_constraint = constraint(temp_middle_FK_CTRL, middle_jnt, "rotate") - child_constraint = constraint(temp_child_FK_CTRL, ik_ctrl, "parent") - - point_constraint = constraint(parent_jnt, temp_parent_FK_CTRL, "translate") # WHEN YOU ROTATE CLAVICLE, NEW PARENT CONTROL TRANSLATES - parent_constraint_two = constraint(temp_middle_FK_CTRL, temp_poleVector_CTRL, "parent") # MAKES THE NEW POLE VECTOR FOLLOW THE PARENT CONTROL - point_constraint_two = constraint(temp_poleVector_CTRL, pole_vector, "translate") # MAKES THE OG POLE VECTOR FOLLOW THE NEW ONE - - return parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two -################# CREATES THE REVERSE CONSTRAINT SETUP ################## - - -#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) - - cmds.autoKeyframe(state=True) - timeline_start, timeline_end, specific_timeline_mode = get_timeline_range() - check_negative_time_range(timeline_start, timeline_end) - - result = get_original_controls() - if result is None: - return - pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt = result - check_anim_layer([pole_vector, ik_ctrl]) - delete_visibility_keys(pole_vector, ik_ctrl) - - fk_ctrls_grps, fk_ctrls = create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt) - temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - - match_rotation_order(ik_ctrl, temp_parent_FK_CTRL) - match_rotation_order(ik_ctrl, temp_middle_FK_CTRL) - match_rotation_order(ik_ctrl, temp_child_FK_CTRL) - ############################# - DONE - ############################# - - - create_reverse_constraint_setup_fk(parent_jnt, temp_parent_FK_CTRL_GRP, temp_parent_FK_CTRL) - create_reverse_constraint_setup_fk(middle_jnt, temp_middle_FK_CTRL_GRP, temp_middle_FK_CTRL) - create_reverse_constraint_setup_fk(ik_ctrl, temp_child_FK_CTRL_GRP, temp_child_FK_CTRL) - create_reverse_constraint_setup_fk(pole_vector, temp_poleVector_CTRL_GRP, temp_poleVector_CTRL) # THIS LOCATOR LACHES ONTO THE OG POLE VECTOR - SO WE KNOW HOW TO BAKE THE OG POLE VECTOR IN THE END - - parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two = reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt) - - # ISOLATE VISIBILITY # - if specific_timeline_mode: - if influence_visibility: - isolate_visibility([ik_ctrl, pole_vector], 0, 1) - isolate_visibility([temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL], 1, 0) - # ISOLATE CONSTRAINT - isolate_constraint([parent_constraint, middle_constraint], [temp_parent_FK_CTRL, temp_middle_FK_CTRL], [parent_jnt, middle_jnt], "Orient") - isolate_constraint([child_constraint], [temp_child_FK_CTRL], [ik_ctrl], "Parent") - - isolate_constraint([point_constraint], [parent_jnt], [temp_parent_FK_CTRL], "Point") - isolate_constraint([parent_constraint_two], [temp_middle_FK_CTRL], [temp_poleVector_CTRL], "Parent") - isolate_constraint([point_constraint_two], [temp_poleVector_CTRL], [pole_vector], "Point") - divide_influence(ik_handle + ".ikBlend", 0, 1) - else: - cmds.setAttr(ik_handle + ".ikBlend", 0) - if influence_visibility: - for original_control in [ik_ctrl, pole_vector]: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True: - cmds.setAttr(temp_shape_node + ".v", 0) - - #CLEAN-UP - hide_controls(temp_poleVector_CTRL) - cmds.cutKey(fk_ctrls, t=(timeline_start, timeline_end), at="translate", option="keys") - hide_attributes("translate", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL) - hide_attributes("scale", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL) - set_control_color(fk_ctrls, 17) - set_control_thickness(fk_ctrls, 2) - - #cmds.keyTangent(fk_ctrls, e=True, itt="auto", ott="auto") - - apply_euler_filter(fk_ctrls) - if clear_keys: - remove_unnecessary_keys(fk_ctrls, ["rotateX", "rotateY", "rotateZ"]) - - cmds.select(ik_handle) - -############################################################################################################################################################################## -########################################################################################################################################################################################################### -############################################################################################################################################################################## - -def get_temporary_setup_selection(): - temp_Selection = cmds.ls(sl=True) - if len(temp_Selection) == 0: - assist_message("To delete a temporary setup, you have to select one of its controls.", 2500) - return temp_Selection - -def check_if_correct_selection(temp_Selection): - if "FK_CTRL" in temp_Selection or "ik_ctrl" in temp_Selection or "pole_vector_ctrl" in temp_Selection: - pass - else: - assist_message("Incorrect selection: " + temp_Selection + " is not a part of any IK FK setup. To delete a setup, select one of its controls and then click the button.", 4500) - -def get_group_selection(selection): - cmds.select(selection) - for i in range(6): - cmds.pickWalk(d="up") - temp_Group = cmds.ls(sl=True)[0] - return temp_Group - -def delete_visibility_keys_after_bake(original_control): - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys") - cmds.setKeyframe(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", v=1) - - -def clean_up(i, attributes, group_Contents): - global timeline_start, timeline_end - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - cmds.autoKeyframe(state=True) - - if group_Contents[i].split("_")[-3:-2][0] == "ISR": - timeline_start, timeline_end = group_Contents[i].split("_")[-2:] - else: - timeline_start, timeline_end = get_timeline_start_end() - - original_control = "_".join(group_Contents[i].split("_")[:-6]) - if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME - original_control = original_control.replace("_dash_", "|") - cmds.bakeResults(original_control, t=(timeline_start, timeline_end), at=attributes, pok=True, sb=bake_interval) - delete_visibility_keys_after_bake(original_control) - - apply_euler_filter([original_control]) - - # #SETS VISIBILITY BACK - # temp_shape_node = cmds.listRelatives(original_control, shapes=True, children=True)[0] - # cmds.setAttr(temp_shape_node + ".v", 1) - - #YOU STORE THE TANGENT TYPE BEFORE THE BAKE, SO THAT INCASE IT WAS IN STEPPED, WE MAKE THE TANGENT STEPPED AGAIN CUZ OTHERWISE IT MESSES UP THE ELBOW AFTERWARDS - return original_control - -def delete_ik_blend(i, group_Contents): - original_control = "_".join(group_Contents[i].split("_")[:-6]) - if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME - original_control = original_control.replace("_dash_", "|") - cmds.cutKey(original_control, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="ikBlend", option="keys") - cmds.setAttr(original_control + ".ikBlend", 1) - return original_control - - -def delete_setup(): - try: - _delete_setup_impl() - except Exception as e: - # Catch all errors to avoid SystemExit - if "SystemExit" not in str(type(e)): - cmds.warning("Delete setup failed: " + str(e)) # Using string concatenation for Python 2.7 compatibility - -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) - temp_group = get_group_selection(selection) - group_contents = cmds.listRelatives(temp_group) - - if "temp_IK_Group" in temp_group: - original_controls = [clean_up(i, ["rotate"], group_contents) for i in range(3,6)] - if clear_keys: - remove_unnecessary_keys(original_controls, ["rotateX", "rotateY", "rotateZ"]) - # remove_unnecessary_keys([original_controls[0], original_controls[2]], ["rotate"]) - # remove_unnecessary_keys([original_controls[1]], ["rotateX"]) - - - elif "temp_FK_Group" in temp_group: - ik_ctrl, pole_vector_ctrl = [clean_up(i, ["translate", "rotate"], group_contents) for i in range(1,3)] - if clear_keys: - remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"]) - remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"]) - ik_handle = delete_ik_blend(3, group_contents) - - - #cmds.lockNode(temp_group, l=False) - if cmds.objExists(temp_group): - cmds.delete(temp_group) - -############################################################################################################################################################################## -########################################################################################################################################################################################################### -############################################################################################################################################################################## - - -#UI LOGIC -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=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=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=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) - - - cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", l="Reduce Keys: ", ncb=1, l1="", cw=(1, 79.5), w=151, vr=False, parent="ikfk_form_layout", - ann="When the animation bakes across, this feature reduces the amount of keyframes on your curves.") - set_form_layout_coordinates("ikfk_form_layout", "ApplyKeyReducer_CheckBox", 105, 5) - - cmds.checkBoxGrp("remove_unnecessary_keys", l="Remove Keys With Same Values: ", ncb=1, l1="", cw = (1, 170), w = 190, vr=False, v1=False, parent ="ikfk_form_layout", - ann="When ticked on, after the bake, static channels get removed.\nStatic channels are curves like scaleX/Y/Z that get baked but have no changes to them across the timeparent_to_child_vector, so they're redundant and take up space.") - set_form_layout_coordinates("ikfk_form_layout", "remove_unnecessary_keys", 133, 11) - - # cmds.floatSliderGrp("remove_keys_threshold", l="Threshold: ", f=True, v=1, cw=(1, 60), w=240, min=1, max=500000, - # parent="ikfk_form_layout", - # ann="The higher the amount, the less keyframes you'll have when applying the key reducer,\nbut you lose out on how precisely the animation gets baked across.") - # set_form_layout_coordinates("remove_keys_threshold", 210, 30) - - cmds.checkBoxGrp("influence_visibility", l="Hide Original Controls: ", ncb=1, l1="", cw=(1, 128), w=166, - vr=False, parent="ikfk_form_layout", v1=True, - ann="When ticked on, after the bake, static channels get removed.\nStatic channels are curves like scaleX/Y/Z that get baked but have no changes to them across the timeparent_to_child_vector, so they're redundant and take up space.") - set_form_layout_coordinates("ikfk_form_layout", "influence_visibility", 160, 9) - - - cmds.floatFieldGrp("Intensity_FloatField", l="Intensity: ", numberOfFields=1, v1=1.0, cw = (1, 52), w = 137, parent ="ikfk_form_layout", - ann="The higher the amount, the less keyframes you'll have when applying the key reducer,\nbut you lose out on how precisely the animation gets baked across.") - set_form_layout_coordinates("ikfk_form_layout", "Intensity_FloatField", 102, 110) - - cmds.intFieldGrp("BakeInterval_IntField", l="Bake Interval: ", numberOfFields=1, v1=1, cw=(1, 85.0), w=198, parent="ikfk_form_layout") - set_form_layout_coordinates("ikfk_form_layout", "BakeInterval_IntField", 75, 1) - - 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=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=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) - # set_form_layout_coordinates("ikfk_form_layout", "Proxy_HRSeparator", 63, -8) - - cmds.showWindow("IK_FK_Switcher") - - -def generateCode(): - # YOU SELECT ONE OF THREE OPTIONS FOR WHAT CODE YOU WANT TO ISOLATE FROM THE SCRIPT: FK TO IK, IK TO FK, DELETE SETUP. - - cmds.scrollField("GenerateCodeOutputWindow", e=True, cl=True) - - code = """ -import maya.cmds as cmds -import maya.mel as mel -import maya.OpenMaya as om - -# from sys import exit # Removed to avoid SystemExit error - -bake_interval = """ + str(cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)) + """ -apply_key_reducer = """ + str(cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True)) + """ -key_reducer_intensity = """ + str(cmds.floatFieldGrp("Intensity_FloatField", q=True, v1=True)) + """ -clear_keys = """ + str(cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)) + """ -influence_visibility = """ + str(cmds.checkBoxGrp("influence_visibility", q=True, v1=True)) + """ - - -################################################################################################################################################################################################################## - -# GENERAL FUNCTIONS -def get_timeline_start_end(): # ---------- DONE - timeline_start = cmds.playbackOptions(min=True, q=True) - timeline_end = cmds.playbackOptions(max=True, q=True) - return timeline_start, timeline_end - -def filter_list_unpack_brackets(*list): # IF LIST HAS 1 ITEM, WE RETURN IT AS ITEM[0] TO REMOVE THE BRACKETS - new_list = [item if len(item) > 1 else item[0] for item in list] - return new_list - -def create_control(name): - points = [(-1, 0, -0), (1, 0, 0), (0, 0, 0), (0, 0, -1), (0, 0, 1), (0, 0, 0), (0, -1, 0), (0, 1, 0)] - temp_control = cmds.curve(n=name, d=1, p=points) - return temp_control - -def apply_euler_filter(controls): - apply_key_reducer = cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True) - key_reducer_intensity = cmds.floatFieldGrp("Intensity_FloatField", q=True, v1=True) - for control in controls: - cmds.select(control) - cmds.filterCurve(control + ".translate", control + ".rotate", f="euler") - if apply_key_reducer: - cmds.filterCurve(control + ".translate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity) - cmds.filterCurve(control + ".rotate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity + 10) - -def hide_attributes(type, *controls): # ---------- DONE - for item in controls: - for attr in ["." + type + "X", "." + type + "Y", "." + type + "Z"]: - cmds.setAttr(item + attr, k=False, l=True, cb=False) - -def hide_controls(*controls): # ---------- DONE - for control in controls: - cmds.setAttr(control + ".v", 0) - -def set_control_color(objects, color): - for obj in objects: - cmds.setAttr(obj + ".overrideEnabled", 1) - cmds.setAttr(obj + ".overrideColor", color) - -def set_control_thickness(objects, value): - for obj in objects: - cmds.setAttr(obj + ".lineWidth", value) - -def set_form_layout_coordinates(form_layout, name, top_coordinates, left_coordinates): # ---------- DONE - #ADJUSTS THE POSITION OF THE UI FEATURES - cmds.formLayout(form_layout, edit=True, attachForm=[(name, "top", top_coordinates), (name, "left", left_coordinates)]) - -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="" + message + "", pos='midCenter', fade=True, fst=time, ck=True) - # Also display warning in Script Editor - cmds.warning(message) - if to_exit: - return # Do not use exit() to avoid SystemExit error - -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 - temp_attribute = [] - if constraint_type == "orient": - temp_attribute = "rotate" - elif constraint_type == "point": - temp_attribute = "translate" - elif constraint_type == "parent": - temp_attribute = ["translate", "rotate"] - return temp_attribute - -def get_locked_attributes(control, attribute): # ---------- OPTIMIZE - #CHECK WHICH ATTRIBUTES ON THE CONTROL ARE LOCKED, SO AS TO KNOW WHICH ONES TO SKIP WHEN APPLYING CONSTRAINTS - if attribute == "parent": - translate_attributes = [".translateX", ".translateY", ".translateZ"] - rotate_attributes = [".rotateX", ".rotateY", ".rotateZ"] - - locked_translate = [attr.lower()[-1:] for attr in translate_attributes if cmds.getAttr(control + attr, lock=True)] - locked_rotate = [attr.lower()[-1:] for attr in rotate_attributes if cmds.getAttr(control + attr, lock=True)] - - locked_attributes = [locked_translate, locked_rotate] - else: - attributes = ["." + attribute + "X", "." + attribute + "Y", "." + attribute + "Z"] - locked_attributes = [attr.lower()[-1:] for attr in attributes if cmds.getAttr(control + attr, lock=True)] - - return locked_attributes - -def constraint(parent, child, type, mo=True): # ---------- DONE - #CONSTRAINT SYSTEM - try: - locked_attributes = get_locked_attributes(child, type) - if type == "parent": - constraint = cmds.parentConstraint(parent, child, maintainOffset = mo, skipTranslate = locked_attributes[0], skipRotate = locked_attributes[1]) - if type == "translate": - constraint = cmds.pointConstraint(parent, child, maintainOffset = mo, skip = locked_attributes) - if type == "rotate": - constraint = cmds.orientConstraint(parent, child, maintainOffset = mo, skip = locked_attributes) - except RuntimeError: - assist_message("Error: Your selected controls are already being influenced by some other source.", 5000) - - - return constraint[0] - -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') - timeline_start, timeline_end = cmds.timeControl(aTimeSlider, q=True, rangeArray=True) - if 1 < (timeline_end - timeline_start): - to_isolate_timeline = True - timeline_end = timeline_end - 1 - else: - to_isolate_timeline = False - timeline_start, timeline_end = get_timeline_start_end() - - return timeline_start, timeline_end, to_isolate_timeline - -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### -def divide_influence(curve, first_value, second_value): - cmds.setKeyframe(curve, t=(timeline_start, timeline_end), value=first_value) - cmds.setKeyframe(curve, t=(timeline_start - 1, timeline_end + 1), value=second_value) - -def get_constraint_blend_index(constraint, control): - # WE'RE TRYING TO FIND THE INDEX AT THE END OF THE CONSTRAINT'S WEIGHT ATTRIBUTE. BECAUSE THERE COULD BE MANY CONSTRAINTS APPLIED ON THE SAME OBJECT, WE CAN'T ALWAYS KNOW WHAT THAT NUMBER WILL BE - for item in cmds.listConnections(constraint, c=True): - if "{0}.{1}W".format(constraint, control) in item: - constraint_index = item[-1:] - blend_index = constraint[-1:] - return constraint_index, blend_index - -def check_if_referenced(original_control, temp_control): - # IF THE RIG IS REFERENCED, WE STORE THE NAME OF THE TEMP LOCATOR WITHOUT THE NAMESPACE, BECAUSE THE CONSTRAINT WE'LL INFLUENCE DON'T HAVE THE NAMESPACE INSIDE - if cmds.referenceQuery(original_control, isNodeReferenced=True) or ":" in original_control: - temp_control = temp_control.split(":")[-1:][0] - return temp_control - -def set_key_frame(constraint_type, control): - temp_attribute = get_constraint_attribute(constraint_type.lower()) - # CHECKS TO SEE IF ORIGINAL CONTROL HAS ANY KEYS ON CURVES ALREADY, IF NOT IT PLACES THEM TO ACTIVATE THE BLEND INDEX - if cmds.keyframe(control, at=temp_attribute, q=True) == None: - cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute) - else: - cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute, i=True) - -def isolate_constraint(constraints, temp_controls, original_controls, constraint_type): - for constraint, temp_control, original_control in zip(constraints, temp_controls, original_controls): - temp_control = check_if_referenced(original_control, temp_control) - - set_key_frame(constraint_type, original_control) - - constraint_index, blend_index = get_constraint_blend_index(constraint, temp_control) - divide_influence(constraint + "." + temp_control + "W" + constraint_index, 1, 0) - divide_influence(original_control + ".blend" + constraint_type + blend_index, 1, 0) - -def isolate_visibility(controls, first_value, second_value): - for control in controls: - temp_shape_nodes = cmds.listRelatives(control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - divide_influence(temp_shape_node + ".v", first_value, second_value) - -def delete_visibility_keys(*original_controls): - if influence_visibility: - for original_control in original_controls: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys") - -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### -####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ###### - -######### CLEAR REDUNDANT KEYS ########## - -def remove_unnecessary_keys(controls, attributes): - tolerance = 0.000002 - for control in controls: - for attr in attributes: - for frame in range(int(timeline_start) + 1, int(timeline_end)): - current_value = cmds.keyframe(control, q=True, t=(frame, frame), at=attr, eval=True)[0] - previous_value = cmds.keyframe(control, q=True, t=(frame - 1, frame - 1), at=attr, eval=True)[0] - #next_value = cmds.keyframe(control, q=True, t=(frame + 1, frame + 1), at=attr, eval=True)[0] - if abs(current_value - previous_value) <= max(1e-09 * max(abs(current_value), abs(previous_value)), tolerance): - cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys") - - # if isclose(current_value, previous_value, abs_tol=tolerance): - # cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys") - - -################# APPLY TO BOTH ############## -def check_if_setup_exists(controls): # DONE - for control in controls: - if cmds.objExists(control + "_temp_IK_Name*") or cmds.objExists(control + "_temp_FK_Name*"): - assist_message("Error: One or more selected controls are part of a pre-existing setup somewhere else on the timeline.", 5000) - -def create_name_reference_groups(contents, suffix): # ---------- DONE - # #CREATES EMPTY GROUPS AS PROXIES FOR REFERENCING THE ACTUAL CONTROLS LATER ON - timeline_mode_keyword = "ISR" if specific_timeline_mode == True else "NIF" - - # BECAUSE SOME NAMES HAVE DASH IN THE MIDDLE IF THEY'RE NOT UNIQUE NAMES, WE HAVE TO REPLACE IT WITH A NEW KEYWORD "_SEPARATOR_" FOR WHEN REFERENCING IT BACK WHEN BAKING. - new_contents = [obj.replace("|", "_dash_") if "|" in obj else obj for obj in contents] - reference_grps = [cmds.group(n=obj + suffix + "_" + timeline_mode_keyword + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end)), em=True) for obj in new_contents] - return reference_grps - -def create_temp_grp(suffix, control_name, controls_to_group): # DONE - temp_group = cmds.group(*controls_to_group, n=control_name + suffix) - #cmds.lockNode(temp_group) - -def match_rotation_order(parent, child): # ---------- DONE - # MATCHES THE ROTATION ORDER FROM THE ORIGINAL SELECTIONS ONTO THE TEMPORARY SETUP CONTROLS - original_rotation_order = cmds.getAttr(parent + ".rotateOrder") - cmds.setAttr(child + ".rotateOrder", original_rotation_order) - -def match_control_scale(parent, children): - # SCALE UP AN OBJECT TO ANOTHER ONE'S BOUNDING BOX SCALE, INCASE IT'S BEEN FREEZE-TRANSFORMED. THIS WAY THE USER DOESN'T HAVE TO MANUALLY ADJUST THE SIZE - children = cmds.ls(children, flatten=True) - if cmds.objectType(parent) == "joint": - parentShapeNode = parent - else: - parentShapeNode = cmds.listRelatives(parent, shapes=True, children=True, pa=True)[0] - - xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(parentShapeNode) - parentDistanceX, parentDistanceY, parentDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin] - - for child in children: - xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(child) - childDistanceX, childDistanceY, childDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin] - - # WE QUERY THE ORIGINAL SCALE OF THE LOCATOR - originalX, originalY, originalZ = cmds.xform(child, q=True, s=True, r=True) - - divisionX, divisionY, divisionZ = [parentDistanceX / childDistanceX, parentDistanceY / childDistanceY, parentDistanceZ / childDistanceZ] - - # WE GET THE FINAL SCALE HERE, WE TAKE THE LONGEST NUMBER AND APPLY THAT TO ALL SCALE AXIS - largestAxis = max([originalX * divisionX, originalY * divisionY, originalZ * divisionZ]) * 3 - newScale = [largestAxis, largestAxis, largestAxis] - if cmds.objectType(parent) == "joint": - if cmds.currentUnit(q=True) == "cm": - cmds.xform(child, scale=(40, 40, 40)) - else: - cmds.xform(child, scale=(0.7, 0.7, 0.7)) - else: - cmds.xform(child, scale=newScale) - - -def check_anim_layer(controls): - root_layer = cmds.animLayer(q=True, r=True) - if root_layer: - layer_list = cmds.animLayer(root_layer, q=True, c=True) - if layer_list != None: - for layer in layer_list: - attribute_list = cmds.animLayer(layer, q=True, attribute=True) - if attribute_list != None: - for attr in attribute_list: - for obj in controls: - if obj in attr: - assist_message("Warning: A control you've selected is in an anim layer. This may mess up the baking process and give bad results", 6000, False) -""" - - if cmds.radioButtonGrp("GenerateCodeOptions_RadioB", q=True, select=True) == 1: - code += """ -############# CREATE IK CONTROLS ################# -def get_original_fk_controls(): # ---------- DONE - fk_ctrls = cmds.ls(sl=True) - if len(fk_ctrls) != 3: - assist_message("Incorrect number of controls selected. To apply an IK setup, you need to select 3 FK controls, in order of parent to child.", 4000) - return None - cmds.select(cl=True) - return fk_ctrls - -def create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl): # ---------- DONE - parent_jnt = cmds.joint(n=parent_ctrl + "_temp_jnt") - middle_jnt = cmds.joint(n=middle_ctrl + "_temp_jnt") - child_jnt = cmds.joint(n=child_ctrl + "_temp_jnt") - - return parent_jnt, middle_jnt, child_jnt - -def create_ik_controls(middle_ctrl, child_ctrl): # ---------- DONE - ik_ctrl = create_control(child_ctrl + "_ik_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - pole_vector_ctrl = create_control(middle_ctrl + "_pole_vector_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end))) - match_control_scale(child_ctrl, [ik_ctrl, pole_vector_ctrl]) - - return ik_ctrl, pole_vector_ctrl - - -def create_ik_handle(parent_jnt, child_jnt, ik_ctrl): # ---------- DONE - # SETS PREFERRED ANGLE ON THE TEMP JOINT CHAIN, APPLIES AN IK HANDLE ON IT - cmds.joint(parent_jnt, e=True, spa=True, ch=True) - temp_IK_Handle = cmds.ikHandle(n=ik_ctrl + "_ikHandle1", sj=parent_jnt, ee=child_jnt)[0] - cmds.matchTransform(temp_IK_Handle, child_jnt) - return temp_IK_Handle - -def create_temp_ik_controls(): # DONE - # STORES ORIGINAL SELECTED FK CONTROLS INTO VARIABLES """ - if len(cmds.ls(sl=True)) == 3: - fk_ctrls = cmds.ls(sl=True) - code += """ - parent_ctrl = """ "\"" + fk_ctrls[0] + "\"" """ - middle_ctrl = """ "\"" + fk_ctrls[1] + "\"" """ - child_ctrl = """ "\"" + fk_ctrls[2] + "\"" """ - fk_ctrls = parent_ctrl, middle_ctrl, child_ctrl - cmds.select(cl=True) - """ - elif len(cmds.ls(sl=True)) == 0: - code += """ - parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls = get_original_fk_controls() """ - else: - assist_message( - "Incorrect number of controls selected. For a specific IK setup, select 3 FK controls. For a generic setup, have no selections.", - 5000) - - code += """ - check_if_setup_exists([parent_ctrl, middle_ctrl, child_ctrl]) - check_anim_layer(fk_ctrls) - - delete_visibility_keys(parent_ctrl, middle_ctrl, child_ctrl) - # CREATES TEMPORARY IK JOINTS AND CONTROLS - parent_jnt, middle_jnt, child_jnt = ik_jnts = create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl) - ik_ctrl, pole_vector_ctrl = ik_ctrls = create_ik_controls(middle_ctrl, child_ctrl) - - # CREATES EMPTY GROUPS FOR REFERENCING THE ORIGINAL CONTROLS WHEN BAKING/DELETING SETUPS - ik_reference_grps = create_name_reference_groups([parent_ctrl, middle_ctrl, child_ctrl], "_temp_IK_Name") - - # STORES WHOLE SETUP IN GROUP - create_temp_grp("_temp_IK_Group", parent_ctrl, [parent_jnt, ik_ctrl, pole_vector_ctrl, ik_reference_grps]) - - return fk_ctrls, ik_jnts, ik_ctrls -############# CREATE IK CONTROLS ################# - - - -############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ################## - -# SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CONTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS -def create_reverse_constraint_setup_ik(parent, child): - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - match_rotation_order(parent, child) - - # POSITIONS THE NEW CONTROLS - if "pole_vector_ctrl" not in child: ################## DON'T LIKE HOW I HAVE TO ADD THESE EXTRA CONDITIONALS IN HERE ##################### - cmds.matchTransform(child, parent, position=True, rotation=True) - - if "jnt" in child: - cmds.makeIdentity(child, apply=True, t=True, r=True, s=True) - - # CONSTRAINTS ORIGINAL CONTROLS TO TEMPORARY, BAKES THE TEMP TO INHERIT ANIM DATA, DELETES CONSTRAINT, CONSTRAINTS TEMPORARY TO ORIGINAL - temp_constraint = constraint(parent, child, "parent") - cmds.bakeResults(child, t=(timeline_start, timeline_end), sb=bake_interval) - cmds.delete(temp_constraint) - - if "pole_vector_ctrl" not in child: - temp_constraint = constraint(child, parent, "rotate") - - return temp_constraint - -def get_vector(position): # ---------- DONE - return om.MVector(position[0], position[1], position[2]) - -def get_pole_vector_position(parent_jnt, middle_jnt, child_jnt): # ---------- DONE - vector_parent = get_vector(cmds.xform(parent_jnt, q=True, t=True, ws=True)) - vector_middle = get_vector(cmds.xform(middle_jnt, q=True, t=True, ws=True)) - vector_child = get_vector(cmds.xform(child_jnt, q=True, t=True, ws=True)) - - parent_to_child_vector = (vector_child - vector_parent) - parent_to_middle_vector = (vector_middle - vector_parent) - - scale_value = (parent_to_child_vector * parent_to_middle_vector) / (parent_to_child_vector * parent_to_child_vector) - parent_to_child_middle_point = parent_to_child_vector * scale_value + vector_parent - - parent_to_middle_length = (vector_middle - vector_parent).length() - middle_to_child_length = (vector_child - vector_middle).length() - total_length = parent_to_middle_length + middle_to_child_length - - pole_vector_position = (vector_middle - parent_to_child_middle_point).normal() * total_length + vector_middle - - return pole_vector_position - -def set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl): - for frame in range(int(timeline_start), int(timeline_end) + 1): - cmds.currentTime(int(frame)) - pole_vector_position = get_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl) - cmds.move(pole_vector_position.x, pole_vector_position.y, pole_vector_position.z, pole_vector_ctrl) - cmds.setKeyframe(pole_vector_ctrl, time=(frame, frame), at="translate") - - -def set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls): - parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls - ik_ctrl, pole_vector_ctrl = ik_ctrls - parent_jnt, middle_jnt, child_jnt = ik_jnts - - # JOINTS - temp_constraint = [create_reverse_constraint_setup_ik(parent, child) for parent, child in zip(fk_ctrls, ik_jnts)] - - # IK CTRL - ik_orient_constraint = create_reverse_constraint_setup_ik(child_jnt, ik_ctrl) - - # POLE-VECTOR - set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl) - #create_reverse_constraint_setup_ik(middle_jnt, pole_vector_ctrl) - - return temp_constraint, ik_orient_constraint -############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ################## - - -#CREATES A TEMPORARY IK SET-UP BY SELECTING EXISTING FK CONTROLS -def fk_to_ik(): - 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) - cmds.autoKeyframe(state=True) - - timeline_start, timeline_end, specific_timeline_mode = get_timeline_range() - check_negative_time_range(timeline_start, timeline_end) - ###### CREATE CONTROLS ###### - result = create_temp_ik_controls() - if result is None: - return - [parent_ctrl, middle_ctrl, child_ctrl], [parent_jnt, middle_jnt, child_jnt], [ik_ctrl, pole_vector_ctrl] = fk_ctrls, ik_jnts, ik_ctrls = result - ###### REPOSITION CONTROLS ###### - temp_constraints, ik_orient_constraint = set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls) - - # CREATES IK HANDLE, ADDS POLE VECTOR CONSTRAINTz - cmds.currentTime(timeline_start) - temp_ik_handle = create_ik_handle(parent_jnt, child_jnt, ik_ctrl) - cmds.parent(temp_ik_handle, ik_ctrl, s=True) - pole_vector_constraint = cmds.poleVectorConstraint(pole_vector_ctrl, temp_ik_handle) - - # MAKES THE IK SETUP WORLD-SPACE - WHEN SHOULDER ROTATES THE IK TRANSLATES BUT DOESN'T ROTATE - point_constraint = constraint(parent_ctrl, parent_jnt, "translate") - - ####### ISOLATE VISIBILITY ###### - if specific_timeline_mode: - if influence_visibility: - isolate_visibility([parent_ctrl, middle_ctrl, child_ctrl], 0, 1) - isolate_visibility([ik_ctrl, pole_vector_ctrl], 1, 0) - ####### ISOLATE CONSTRAINT ###### - isolate_constraint(temp_constraints, ik_jnts, fk_ctrls, "Orient") - else: - if influence_visibility: - for original_control in fk_ctrls: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True: - cmds.setAttr(temp_shape_node + ".v", 0) - - - #isolate_constraint([ik_orient_constraint], [ik_ctrl], [child_jnt], "Orient") - #isolate_constraint([point_constraint], [parent_ctrl], [parent_jnt], "Point") - - ############################# - DONE - ############################# - - #CLEAN-UP # - COULD MAKE INTO ITS OWN FUNCTION - hide_controls(parent_jnt, temp_ik_handle) - cmds.cutKey(pole_vector_ctrl, t=(timeline_start, timeline_end), at="rotate", option="keys") - hide_attributes("rotate", pole_vector_ctrl) - hide_attributes("scale", ik_ctrl, pole_vector_ctrl) - set_control_color(ik_ctrls, 18) - set_control_thickness(ik_ctrls, 2) - #set_control_thickness([ik_ctrl, pole_vector_ctrl], 2) - #cmds.keyTangent(ik_ctrls, e=True, itt="auto", ott="auto") - apply_euler_filter(ik_ctrls) - if clear_keys: - remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"]) - remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"]) - - cmds.select(ik_ctrl) - -fk_to_ik() - """ - elif cmds.radioButtonGrp("GenerateCodeOptions_RadioB", q=True, select=True) == 2: - code += """ - -################ GET ORIGINAL CONTROLS #################### -def get_original_ik_controls(): - ik_ctrls = cmds.ls(sl=True) - if len(ik_ctrls) != 2: - assist_message("Incorrect number of controls selected. To apply an FK setup, you need to select the Pole Vector first and then the IK Control, in order.", 4000) - return None - cmds.select(cl=True) - return ik_ctrls - -def get_ik_handle(pole_vector): # ----- NOT DONE, ADD MORE CONDITIONALS - #FROM THE POLE VECTOR, WE DERIVE THE SELECTION OF THE PARENT AND MIDDLE JOINT THAT THE IK HANDLE INFLUENCES, AND STORE THEM IN VARIABLES - cmds.select(pole_vector, hi=True) - pole_vector_hierarchy = cmds.ls(sl=True) - - for obj in pole_vector_hierarchy: - poleVectorConstraint = cmds.listConnections(obj, type = "poleVectorConstraint") - ik_handle = cmds.listConnections(poleVectorConstraint, type = "ikHandle") - if ik_handle != None: - break - if ik_handle == None: - keywords = "_".join(pole_vector.split("_")[:3]) - ik_handle = cmds.ls(keywords + "*", type="ikHandle") - if ik_handle == None: - keywords = "_".join(pole_vector.split("_")[:2]) - ik_handle = cmds.ls(keywords + "*", type="ikHandle") - - if ik_handle == None or len(ik_handle) == 0: - assist_message("Couldn't obtain IK handle from the rig. Selection order must be Pole Vector first, then IK control. If that doesn't work, this feature is incompatible with this rig.", 4000) - return ik_handle[0] - -def get_ik_handle_joints(ik_handle): - parent_jnt, middle_jnt = cmds.ikHandle(ik_handle, q=True, jl=True) - return parent_jnt, middle_jnt - -def get_original_controls(): - # SEPARATES THE SELECTED CONTROLS INTO THEIR OWN VARIABLES - """ - if len(cmds.ls(sl=True)) == 2: - ik_ctrls = cmds.ls(sl=True) - code += """ - pole_vector = """ "\"" + ik_ctrls[0] + "\"" """ - ik_ctrl = """ "\"" + ik_ctrls[1] + "\"" """ - cmds.select(cl=True) - """ - elif len(cmds.ls(sl=True)) == 0: - code += """ - pole_vector, ik_ctrl = get_original_ik_controls() - """ - else: - assist_message( - "Incorrect number of controls selected. For a specific FK setup, select the Pole Vector and IK Control in order. For a generic setup, have no selections.", - 5000) - code += """ - check_if_setup_exists([pole_vector, ik_ctrl]) - ik_handle = get_ik_handle(pole_vector) - parent_jnt, middle_jnt = get_ik_handle_joints(ik_handle) - - return pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt -################ GET ORIGINAL CONTROLS #################### - -################ CREATE TEMP CONTROLS #################### -def create_fk_controls(*controls): - fk_ctrls = [create_control(control + "_temp_FK_CTRL") for control in controls] - fk_ctrls_grps = [cmds.group(fk_ctrl, n=fk_ctrl + "_GRP") for fk_ctrl in fk_ctrls] - return filter_list_unpack_brackets(fk_ctrls_grps, fk_ctrls) - -def create_parent_hierarchy_offsets_controls(offsets, controls): - # SHIFTS THE OFFSETS ELEMENTS SO THAT THE SECOND OFFSET MATCHES THE FIRST CONTROL AND WE PROPERLY PARENT THEM - for offset, control in zip((offsets[1:] + offsets[:1])[:-1], controls[:-1]): - cmds.parent(offset, control) - -def create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt): - # CREATE 3 TEMP LOCATORS, ADD A GROUP ON TOP OF THEM AND PARENT THEM TO EACH OTHER - fk_ctrls_grps, fk_ctrls = create_fk_controls(parent_jnt, middle_jnt, ik_ctrl, pole_vector) - temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - match_control_scale(ik_ctrl, [temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL]) - - create_parent_hierarchy_offsets_controls(fk_ctrls_grps, fk_ctrls) - cmds.parent(temp_poleVector_CTRL_GRP, temp_parent_FK_CTRL) # THE PREVIOUS FUNCTION DOESN'T PLACE THE TEMP POLE VECTOR UNDER THE RIGHT PARENT, SO HERE WE ADJUST THAT - - # CREATES NAME REFERENCE GROUPS FOR ALL ORIGINAL CONTROLS THAT NEED TO BE BAKED WHEN DELETING THE SETUP, PLACES ALL THE CONTENTS IN A NEW TEMP GROUP - reference_grps = create_name_reference_groups([ik_ctrl, pole_vector, ik_handle], "_temp_FK_Name") - create_temp_grp("_temp_FK_Group", parent_jnt, [temp_parent_FK_CTRL_GRP, reference_grps]) - - return fk_ctrls_grps, fk_ctrls -################ CREATE TEMP CONTROLS #################### - -################# CREATES THE REVERSE CONSTRAINT SETUP ################## - -#SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CO NTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS -def create_reverse_constraint_setup_fk(parent, group, child): - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - - cmds.matchTransform(group, parent, position=True, rotation=True) - temp_constraint = constraint(parent, child, "parent", True) - - cmds.bakeResults(child, t=(timeline_start, timeline_end), simulation=False, sb=bake_interval) - cmds.delete(temp_constraint) - -def reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt): - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - # REVERSE CONSTRAINT FROM THE TEMP CONTROLS TO THE ORIGINALS - parent_constraint = constraint(temp_parent_FK_CTRL, parent_jnt, "rotate") - middle_constraint = constraint(temp_middle_FK_CTRL, middle_jnt, "rotate") - child_constraint = constraint(temp_child_FK_CTRL, ik_ctrl, "parent") - - point_constraint = constraint(parent_jnt, temp_parent_FK_CTRL, "translate") # WHEN YOU ROTATE CLAVICLE, NEW PARENT CONTROL TRANSLATES - parent_constraint_two = constraint(temp_middle_FK_CTRL, temp_poleVector_CTRL, "parent") # MAKES THE NEW POLE VECTOR FOLLOW THE PARENT CONTROL - point_constraint_two = constraint(temp_poleVector_CTRL, pole_vector, "translate") # MAKES THE OG POLE VECTOR FOLLOW THE NEW ONE - - return parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two -################# CREATES THE REVERSE CONSTRAINT SETUP ################## - - -#CREATES A TEMPORARY FK SET-UP BY SELECTING EXISTING IK CONTROLS -def ik_to_fk(): - 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) - - cmds.autoKeyframe(state=True) - timeline_start, timeline_end, specific_timeline_mode = get_timeline_range() - check_negative_time_range(timeline_start, timeline_end) - - result = get_original_controls() - if result is None: - return - pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt = result - check_anim_layer([pole_vector, ik_ctrl]) - delete_visibility_keys(pole_vector, ik_ctrl) - - fk_ctrls_grps, fk_ctrls = create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt) - temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps - temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls - - match_rotation_order(ik_ctrl, temp_parent_FK_CTRL) - match_rotation_order(ik_ctrl, temp_middle_FK_CTRL) - match_rotation_order(ik_ctrl, temp_child_FK_CTRL) - ############################# - DONE - ############################# - - - create_reverse_constraint_setup_fk(parent_jnt, temp_parent_FK_CTRL_GRP, temp_parent_FK_CTRL) - create_reverse_constraint_setup_fk(middle_jnt, temp_middle_FK_CTRL_GRP, temp_middle_FK_CTRL) - create_reverse_constraint_setup_fk(ik_ctrl, temp_child_FK_CTRL_GRP, temp_child_FK_CTRL) - create_reverse_constraint_setup_fk(pole_vector, temp_poleVector_CTRL_GRP, temp_poleVector_CTRL) # THIS LOCATOR LACHES ONTO THE OG POLE VECTOR - SO WE KNOW HOW TO BAKE THE OG POLE VECTOR IN THE END - - parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two = reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt) - - # ISOLATE VISIBILITY # - if specific_timeline_mode: - if influence_visibility: - isolate_visibility([ik_ctrl, pole_vector], 0, 1) - isolate_visibility([temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL], 1, 0) - # ISOLATE CONSTRAINT - isolate_constraint([parent_constraint, middle_constraint], [temp_parent_FK_CTRL, temp_middle_FK_CTRL], [parent_jnt, middle_jnt], "Orient") - isolate_constraint([child_constraint], [temp_child_FK_CTRL], [ik_ctrl], "Parent") - - isolate_constraint([point_constraint], [parent_jnt], [temp_parent_FK_CTRL], "Point") - isolate_constraint([parent_constraint_two], [temp_middle_FK_CTRL], [temp_poleVector_CTRL], "Parent") - isolate_constraint([point_constraint_two], [temp_poleVector_CTRL], [pole_vector], "Point") - divide_influence(ik_handle + ".ikBlend", 0, 1) - else: - cmds.setAttr(ik_handle + ".ikBlend", 0) - if influence_visibility: - for original_control in [ik_ctrl, pole_vector]: - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True: - cmds.setAttr(temp_shape_node + ".v", 0) - - #CLEAN-UP - hide_controls(temp_poleVector_CTRL) - cmds.cutKey(fk_ctrls, t=(timeline_start, timeline_end), at="translate", option="keys") - hide_attributes("translate", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL) - hide_attributes("scale", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL) - set_control_color(fk_ctrls, 17) - set_control_thickness(fk_ctrls, 2) - - #cmds.keyTangent(fk_ctrls, e=True, itt="auto", ott="auto") - - apply_euler_filter(fk_ctrls) - if clear_keys: - remove_unnecessary_keys(fk_ctrls, ["rotateX", "rotateY", "rotateZ"]) - - cmds.select(temp_parent_FK_CTRL) - -ik_to_fk() - -""" - elif cmds.radioButtonGrp("GenerateCodeOptions_RadioB", q=True, select=True) == 3: - code += """ - -def get_temporary_setup_selection(): - temp_Selection = cmds.ls(sl=True) - if len(temp_Selection) == 0: - assist_message("To delete a temporary setup, you have to select one of its controls.", 2500) - return temp_Selection - -def check_if_correct_selection(temp_Selection): - if "FK_CTRL" in temp_Selection or "ik_ctrl" in temp_Selection or "pole_vector_ctrl" in temp_Selection: - pass - else: - assist_message("Incorrect selection: " + temp_Selection + " is not a part of any IK FK setup. To delete a setup, select one of its controls and then click the button.", 4500) - -def get_group_selection(selection): - cmds.select(selection) - for i in range(6): - cmds.pickWalk(d="up") - temp_Group = cmds.ls(sl=True)[0] - return temp_Group - -def delete_visibility_keys_after_bake(original_control): - temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True) - if temp_shape_nodes != None: - for temp_shape_node in temp_shape_nodes: - cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys") - cmds.setKeyframe(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", v=1) - - -def clean_up(i, attributes, group_Contents): - global timeline_start, timeline_end - bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True) - cmds.autoKeyframe(state=True) - - if group_Contents[i].split("_")[-3:-2][0] == "ISR": - timeline_start, timeline_end = group_Contents[i].split("_")[-2:] - else: - timeline_start, timeline_end = get_timeline_start_end() - - original_control = "_".join(group_Contents[i].split("_")[:-6]) - if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME - original_control = original_control.replace("_dash_", "|") - cmds.bakeResults(original_control, t=(timeline_start, timeline_end), at=attributes, pok=True, sb=bake_interval) - delete_visibility_keys_after_bake(original_control) - - apply_euler_filter([original_control]) - - # #SETS VISIBILITY BACK - # temp_shape_node = cmds.listRelatives(original_control, shapes=True, children=True)[0] - # cmds.setAttr(temp_shape_node + ".v", 1) - - #YOU STORE THE TANGENT TYPE BEFORE THE BAKE, SO THAT INCASE IT WAS IN STEPPED, WE MAKE THE TANGENT STEPPED AGAIN CUZ OTHERWISE IT MESSES UP THE ELBOW AFTERWARDS - return original_control - -def delete_ik_blend(i, group_Contents): - original_control = "_".join(group_Contents[i].split("_")[:-6]) - if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME - original_control = original_control.replace("_dash_", "|") - cmds.cutKey(original_control, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="ikBlend", option="keys") - cmds.setAttr(original_control + ".ikBlend", 1) - return original_control - - -def delete_setup(): - #BAKES THE PREVIOUS CONTROLS, CLEANS UP THE CURVES, DELETES CURRENT CONTROLS AND BRINGS BACK ORIGINALS - """ - selected_controls = cmds.ls(sl=True) - if len(selected_controls) > 0: - for i in range(6): - cmds.pickWalk(d="up") - for obj in cmds.ls(sl=True): - if "temp_IK_Group" in obj or "temp_FK_Group" in obj: - pass - else: - assist_message( - "Incorrect selection. To generate the code for deleting an IK or FK setup, select one of its controls and then click the button.", - 4500) - code += """ - temp_selection = """ + str(selected_controls) + """ - """ - - elif len(cmds.ls(sl=True)) == 0: - code += """ - temp_selection = get_temporary_setup_selection()""" - - code += """ - for selection in temp_selection: - if cmds.objExists(selection): - check_if_correct_selection(selection) - temp_group = get_group_selection(selection) - group_contents = cmds.listRelatives(temp_group) - - if "temp_IK_Group" in temp_group: - original_controls = [clean_up(i, ["rotate"], group_contents) for i in range(3,6)] - if clear_keys: - remove_unnecessary_keys(original_controls, ["rotateX", "rotateY", "rotateZ"]) - # remove_unnecessary_keys([original_controls[0], original_controls[2]], ["rotate"]) - # remove_unnecessary_keys([original_controls[1]], ["rotateX"]) - - - elif "temp_FK_Group" in temp_group: - ik_ctrl, pole_vector_ctrl = [clean_up(i, ["translate", "rotate"], group_contents) for i in range(1,3)] - if clear_keys: - remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"]) - remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"]) - ik_handle = delete_ik_blend(3, group_contents) - - - #cmds.lockNode(temp_group, l=False) - if cmds.objExists(temp_group): - cmds.delete(temp_group) - -delete_setup() - -""" - - cmds.scrollField("GenerateCodeOutputWindow", e=True, tx=code) -def extraOptions(): - if cmds.window("Extra_Options", ex=True): - cmds.deleteUI("Extra_Options") - - cmds.window("Extra_Options", title="Extra Options", wh=[344, 242], s=False) - cmds.formLayout("ikfk_form_layout_extra", numberOfDivisions=100, w=343, h=240) - - 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=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" - "Important note: Whatever values you have for the settings in the main window, those values will be stored within the code you generate.") - set_form_layout_coordinates("ikfk_form_layout_extra", "Generate_Code_Button", 17, 16) - - cmds.radioButtonGrp("GenerateCodeOptions_RadioB", vr=True, numberOfRadioButtons=3, en1=True, l1="FK to IK", - l2="IK to FK", l3="Delete Setup", parent="ikfk_form_layout_extra") - cmds.radioButtonGrp("GenerateCodeOptions_RadioB", e=True, select=1) - set_form_layout_coordinates("ikfk_form_layout_extra","GenerateCodeOptions_RadioB", 11.5, 135) - - cmds.scrollField("GenerateCodeOutputWindow", width=312, height=150) - set_form_layout_coordinates("ikfk_form_layout_extra", "GenerateCodeOutputWindow", 75, 16) - - cmds.showWindow("Extra_Options") - - -# Export main functions for external use -__all__ = [ - 'fk_to_ik', - 'ik_to_fk', - 'delete_setup', - 'documentation', - 'extraOptions', - 'user_interface', - 'show', -] - - -def show(): - """ - Convenience function: Launch IK/FK Switcher UI - - Usage: - import animation_tools.ik_fk_switcher as ikfk - ikfk.show() - """ - user_interface() - - -if __name__ == "__main__": - user_interface() \ No newline at end of file diff --git a/2025/scripts/animation_tools/ikfx_switch.py b/2025/scripts/animation_tools/ikfx_switch.py new file mode 100644 index 0000000..95b9ebf --- /dev/null +++ b/2025/scripts/animation_tools/ikfx_switch.py @@ -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() diff --git a/2025/shelves/shelf_Nexus_Animation.mel b/2025/shelves/shelf_Nexus_Animation.mel index b3ff81f..05699fc 100644 --- a/2025/shelves/shelf_Nexus_Animation.mel +++ b/2025/shelves/shelf_Nexus_Animation.mel @@ -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 ; - }