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 '''