import pymel.core as pm import maya.OpenMaya as om import logging """ // Universal IK FK // version 3.1 // November 23, 2023 // Monika Gelbmann // monikagelbmann@gmail.com // www.monikagelbmann.com Universal IK FK Switch and Match Tool DESCRIPTION: This script lets you switch and match Poses between IK/FK controls in the animation scene. Works for Riggs that don't have IK/FK match built in and requires only Standard FK controls and IK Pole Vector Setup. The Controls are defined once and can be stored in Node for easy re use throughout the animation. INSTALLATION: a) Copy the file (mog_ikFkSwitch.py) to your Maya scripts directory. On Windows that is Documents/maya/20xx/scripts/ b) Open Maya. In the Script Editor (Python), past the following code: import pymel.core as pm import mog_ikFkSwitchFree as mog_ikFkSwitchFree import imp imp.reload(mog_ikFkSwitchFree) mog_ikFkSwitchFree.FkIk_UI() c) Hit execute (or Ctrl Enter) USAGE: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> SETUP STORE NODE FOR LIMB MATCHING <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 1. Define Limb to work on This always needs to be defined bofore loading/storing/switching. Sides are treated seperately 2. Define Ctrls necessary by selecting them and hitting the << button << FK1: Upper arm << FK2: Lower arm << FK3: FK Hand << IK Ctrl: IK Hand << IK Pole: IK Ellbow pole vector (rig need to have pole vecotr for ik ellbow control) << Switch Ctrl: The ctrl that is used to switch between ik and fk mode << Switch Attr: The attribute that is dialed to switch. It can be highlighted in the channel box and hit << 3. Define Behavior >> Knee Bend: Primary bend axis for the knee/ellbow. Bend the knee/ellbow 90 degree. What axis does it bend? Knee Bend values for common rigs: Malcolm(c Animschool): : +X Armx, -X Legs Steward(c Animation Mentor): -Y Arms, +Z Legs Jack(c Animsquad): -Y Arms, +X Legs Norman: +Z Arms/Legs Advanced Skeleton: -Z Arms/Legs >> Rotation Offset: Some Riggs have different orientations in their IK and FK ctrls and joints. This becomes obvious when running 'Match' from fk to ik and seeing a 90 degree offset in the wrist Set the offset with that value and run 'Match' again to align them Rotation Offsets for common rigs (X,Y,Z): Malcolm(c Animschool): : (0, 0, 180) Right Arm/Leg, (0, 180, 0) Left Arm/Leg Steward(c Animation Mentor): (180, 0, 0) Right Arm, (-20,-90, 0) Right Leg, (160,-90, 0) Left Leg Jack(c Animsquad): (-180, 0, 0) Right Side Norman: (0, -90, -90) Right Arm, (0, 90, -90) Left Arm, (-90,-90,0) Right Leg, (90,-90,0) Left Leg Advanced Skeleton: (90,0,175) Right Arm, (-90,0,175) Left Arm 4.Save/Update: Stores the Setup Node for this Limb in the Scene as a Null node >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> UPDATE A EXISTING STORE NODE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 1. Load: Selected a Ctl in viewport, if a Setup Node exists in the Scene, load it into the Setup fields in the UI\ 2. Make modifications 3. Save/Update and confirm to overwrite existing Store node >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> SWITCH / MATCH <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 1. Select a Ctrl in the viewport and click "Load Store Node from Selections". When a Setup Node exists a green bar will light up 2. Match IK >> FK or FK >> IK to match the limb between the modes >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ADDITONAL BUTTONS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Switch IK/FK: Simple switches between IK/FK modes (does not do any matching) Select all IK or FK ctrls: Select all nodes for this mode as stored in the Seup node >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PRO BUTTONS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Key all IK/FK: Creates a keyframe for all IK/FK Ctrls on current frame Bake IK/FK Range: Bakes the entire timeline frame range to ik or fk. Leaves source keys clean Bake IK/FK AllKeys: Bakes all keyframes in the timeline to ik or fk. Leaves source keys clean. Make sure your frame range is set to include all keys you want to bake. Export Store Nodes: Save all store nodes into .ma/.mb Store File saved in the same location as the scene. The file name is the same as the scene + _IKFKSTORE and located in the same folder as the file Already existing store nodes in the scene will be overwritten Import Store Nodes: Opens file dialogue to select the previously exported Store File LIMITATIONS: - Pole Vector Control is required and will not run if controlled with attribute - Works only on Referenced Riggs Future Improvements/Optimzations planned: - Make script work/not error if there is no polevector VERSIONS: 3.1 - November 23, 20234 - Added ability to look for Referenced Store Nodes (Save Store node in Rigg files) 3.0 - Oktober 06, 2021 - Update for Maya 2022 2.0 - January 27, 2021 - UI update for Maya 2020 1.12 - September 27, 2020 - Fixed isses with 'Switch' attribute for Baking (PRO) 1.11 - July 21, 2020 - Bug fix FK to IK Match pole Vector computation for locked fk controls 1.10 - July 05, 2020 - Pro version: added option to Bake All Keyframes in Range IK/FK and FK/IK in addtion to Bake every Frame 1.9 - June 03, 2020 Bug fix position snap for fk controls with transformation 1.8 - September 20, 2019 - Added knee/ellbow Bend Attribute to solve pole vector flip issues Added more Info about Rotation Offset and Bend Angle to Help tab Evaluation update bug fixes Pro version: changes save format from .fbx to .ma/.mb 1.7 - May 25, 2018 - Too small Joint orientation on Malcolm fixed 1.6 - August 27, 2017 - Offset Value Bug fix. Storing float values now. 1.5 - August 27, 2017 - Blend Value 1 - 10 bug fix Pro version: Bake range and Set Keyframe for all IK/FK ctrls Free version: Select all ik/fk ctrls 1.4 - April 24, 2017 - Beta release. New interface. Auto detect limbs by selecting 1.1 - Jan 12, 2017 - Improvement to interface and bug fixes. 1.0 - Jan 07, 2017 - Initial Release. // Questions/comments/bugs/issues to // monikagelbmann@gmail.com """ debug = False debugZero = False _logger = logging.getLogger(__name__) class FkIk_UI: COLOR_PRESETS = { "grey": (.5, .5, .5), "lightgrey": (.7, .7, .7), "darkgrey": (.25, .25, .25), "turquoise": (.3, .55, .55)} def __init__(self): global win win ='ikfkswitchUI_' # on off logging #logging.basicConfig(level=logging.INFO) _logger.disabled = not debug try: if pm.window("ikFkSwitch_UI", exists=True): pm.deleteUI("ikFkSwitch_UI") except Exception as e: pass windowWidth = 350 windowHeight = 250 window = pm.window("ikFkSwitch_UI", width=windowWidth, height=windowHeight, title="Universal IK FK Free") topLevelColumn = pm.columnLayout(adjustableColumn=True, columnAlign="center") #Setup Tabs #every child creates new tab tabHeight = 350 tabWidth = 300 scrollWidth = tabWidth - 40 riggTab = self.initializeTab(tabHeight, tabWidth) pm.setParent("..") #Display window pm.showWindow("ikFkSwitch_UI") def initializeTab(self, tabHeight, tabWidth): frameWidth = tabWidth - 20 mainColumnLayout = pm.columnLayout(win+"mainColumnLayout", w=tabWidth,columnAttach=('left', 10)) pm.setParent(win + "mainColumnLayout") ## ####################### SETUP FRAME ############################## ## pm.frameLayout(win+"setupFrameLayout", w=tabWidth, label="Setup Store Node",collapsable=True, collapse=True ) pm.separator(h=10) pm.text('1. Choose Limb ') pm.separator(h=10) pm.columnLayout(win + 'ctrlinputColumn', cal='left', columnWidth=20) pm.rowLayout(win+"switchLimb", numberOfColumns=5) self.collection2 = pm.radioCollection(win+'limbRadioCollt') self.rb1 = pm.radioButton(win+'R_arm', w=frameWidth/5, label='R Arm') self.rb2 = pm.radioButton(win+'L_arm', w=frameWidth/5, label='L Arm') self.rb3 = pm.radioButton(win+'R_leg', w=frameWidth/5, label='R Leg') self.rb4 = pm.radioButton(win+'L_leg', w=frameWidth/5, label='L Leg') pm.button(label=' From Sel ', w=frameWidth/5, command=lambda a:self.autoDetectSideAndLimbWin()) pm.setParent(win+"setupFrameLayout") pm.separator(h=10) pm.text('2. Define Controls') pm.separator(h=10) pm.textFieldButtonGrp(win+'fkshldrTfb', label='', text='', cw3=(0,200,100), ad3=3, buttonLabel=' < FK Upper Limb', bc=lambda:self.inputSelTfb("fkshldrTfb"), columnAlign3=("right", "left", "left")) pm.textFieldButtonGrp(win+'fkellbowTfb', label='', text='', cw3=(0,200,100), ad3=3, buttonLabel='< FK Lower Limb', bc=lambda:self.inputSelTfb("fkellbowTfb")) pm.textFieldButtonGrp(win+'fkwristTfb', label='', text='', cw3=(0,200,100), ad3=3, buttonLabel='< FK Wrist/Foot', bc=lambda:self.inputSelTfb("fkwristTfb")) pm.textFieldButtonGrp(win+'ikwristTfb', label='', text='',cw3=(0,200,100), ad3=3, buttonLabel='< IK Wrist/Foot', bc=lambda:self.inputSelTfb("ikwristTfb")) pm.textFieldButtonGrp(win+'ikpvTfb', label='', text='', cw3=(0,200,100), ad3=3, buttonLabel='< Pole Vector', bc=lambda:self.inputSelTfb("ikpvTfb")) pm.setParent(win+"setupFrameLayout") # pm.setParent(self.UIElements["mainColumnLayout"]) # pm.textFieldButtonGrp(win+'switchCtrlTfb', label='', cw3=(0,200,100), ad3=3, text='', buttonLabel='< Switch Ctrl', bc=lambda:self.inputSelTfb("switchCtrlTfb")) pm.textFieldButtonGrp(win+'switchAttrTfb', label='', cw3=(0,200,100), ad3=3, text='', buttonLabel='< Switch Attr', bc=lambda:self.inputChannelboxSelectionTbf("switchAttrTfb")) pm.rowLayout(win+"ikIsValueRow", numberOfColumns=3) pm.text('Attribute on 0 is', w=frameWidth/3) collection2 = pm.radioCollection(win+'switch0isfkTfb') rb1 = pm.radioButton(win+'attr0IsIk', label='IK mode', w=frameWidth/3) rb2 = pm.radioButton(win+'attr0IsFk', label='FK mode', w=frameWidth/3) pm.radioCollection(win+"switch0isfkTfb", e=1, select=rb2) pm.setParent(win+"setupFrameLayout") pm.rowLayout(win+"ikIsRangeRow", numberOfColumns=3) pm.text('Attribute Range', w=frameWidth/3) collection2 = pm.radioCollection(win+'switchAttrRangeTfb') rb1 = pm.radioButton(win+'attr1', label='0 to 1', w=frameWidth/3) rb2 = pm.radioButton(win+'attr10', label='0 to 10', w=frameWidth/3) pm.radioCollection(win+"switchAttrRangeTfb", e=1, select=rb1) pm.setParent(win+"setupFrameLayout") pm.rowLayout(win+"bendKneeAxisRowTfb", numberOfColumns=7) pm.text('Knee Bend', w=frameWidth/4) collection2 = pm.radioCollection(win+'bendKneeAxisTfb') rb1 = pm.radioButton(win+'pX', label='+X', w=frameWidth/8) rb2 = pm.radioButton(win+'nX', label='-X', w=frameWidth/8) rb3 = pm.radioButton(win+'pY', label='+Y', w=frameWidth/8) rb4 = pm.radioButton(win+'nY', label='-Y', w=frameWidth/8) rb5 = pm.radioButton(win+'pZ', label='+Z', w=frameWidth/8) rb6 = pm.radioButton(win+'nZ', label='-Z', w=frameWidth/8) pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=rb1) pm.setParent(win+"setupFrameLayout") pm.rowColumnLayout("rotOffsetRow", numberOfColumns=4, columnWidth=[(1,frameWidth/4), (2,frameWidth/4), (3,frameWidth/4)]) pm.text(l='rotOffset') pm.textField(win+'rotOffsetX', tx=0) pm.textField(win+'rotOffsetY', tx=0) pm.textField(win+'rotOffsetZ', tx=0) pm.setParent(win + "setupFrameLayout") # store for clearing self.inputTxtFldBtnGrps = [win+"fkshldrTfb",win+"fkellbowTfb",win+"fkwristTfb", win+"ikwristTfb", win+"ikpvTfb",win+"switchCtrlTfb",win+"switchAttrTfb"] self.inputTxtFlds = [ win+"rotOffsetX", win+"rotOffsetY", win+"rotOffsetZ"] pm.button(label=' Save/Update ', w=300, bgc=self.COLOR_PRESETS["turquoise"], command=lambda a:self.saveIkFkCtrlsWin()) pm.button(label='Load', bgc=self.COLOR_PRESETS["darkgrey"], w=300, h=20, command=lambda a: self.loadIkFkCtrlsWin()) pm.button(label='clear Fields', bgc=self.COLOR_PRESETS["darkgrey"], w=300, h=20,command=lambda a: self.clearInputFields()) pm.separator(h=10) ## ## ####################### HELP FRAME ############################## ## pm.setParent(win + "mainColumnLayout") pm.frameLayout(win + "helpFrameLayout", w=300, label="Help", collapsable=True, collapse=True) pm.columnLayout(win + 'helpColumn', cal='left', columnWidth=300) pm.scrollField(w=300, wordWrap = True, editable=False, tx= 'Use <<< Button to Fill the Fields by what you have selected in the Viewport.\n\n' + 'Switch Ctrl: Choose the Ctrls that sets IK/FK mode\n\n' + 'Switch Attr: Highlight the "ikfk" Attribute in the Channelbox and hit <<\n\n' + 'Knee Bend: What axis does the knee/ellbow bend?\n' + 'Malcolm(Animschool): +X Armx, -X Legs\n' + 'Steward(AnimationMentor): -Y Arms, +Z Legs \n' + 'Jack(Animsquad): -Y Arms, +X Legs\n' + 'Norman: -Y Arms, +X Legs\n' + 'Advanced Skeleeton: -Z Arms and Legs\n\n' + 'Rotation Offset: Orientation difference of IK/FK wrist\n' + 'Malcolm(Animschool): (0,0,180) R Arm/Leg, (0,180,0) L Arm/Leg\n' + 'Steward(AnimationMentor): (180,0,0) R Arm, (-20,-90,0) R Leg, (160,-90,0) L Leg\n' + 'Jack(Animsquad): (-180,0,0) R Arm/Leg \n' + 'Norman: (0,-90,-90) R Arm, (0,90,-90) L Arm, (-90,-90,0) R Leg, (90,-90,0) L Leg\n' + 'Adanced Skeleton: (90,0,175) R Arm, (-90,0,175) L Arm\n\n' + 'Match IK/FK: Does the matching between IK/FK\n\n' + 'Switch IK/FK: Simple switches between IK/FK modes (does not do any matching)\n\n' + 'Steps:\n' + '1. Fill Setup Input Fields\n' + '2. Hit "Save/Update"\n' + '3. Hit Load \"Store Node from Selection\"\n' + '4. Hit "Match IK>FK" or "Match FK>IK"\n\n' + 'Find more details in \"how to install and use.txt\"' ) pm.separator(h=10) ## ####################### MATCH FRAME ############################## ## pm.setParent(win+"mainColumnLayout") pm.frameLayout(win + "matchFrameLayout", w=300, label="Match and Switch", collapsable=True ) pm.text('3. Match / Switch') pm.separator(h=10) pm.button(label='Load Store Node from Selection', w=300, command=lambda a: self.findStoreNodeFromSelectionWin()) self.readyText = pm.text(win + 'readyText', label='Not Ready.', align='left', bgc=(.6,.4,.4)) pm.separator(h=10) pm.rowColumnLayout(win+"matchIKRow", numberOfColumns=2, columnWidth=[(1,150), (2,150)]) pm.button(label="Match IK >> FK", bgc=self.COLOR_PRESETS["turquoise"], command=lambda a: self.matchIkFkWin(tofk=1)) pm.button(label="Match FK >> IK", bgc=self.COLOR_PRESETS["turquoise"], command=lambda a: self.matchIkFkWin(tofk=0)) pm.setParent(win+"matchFrameLayout") pm.rowColumnLayout(win+"switchIKRow", numberOfColumns=2, columnWidth=[(1,150), (2,150)]) pm.button(label="Switch IK", command=lambda a: self.switchIkFkWin()) pm.button(label="Switch FK", command=lambda a: self.switchFkIkWin()) pm.button(label="Select all IK", command=lambda a: self.selectAll(fk=0)) pm.button(label="Select all FK", command=lambda a: self.selectAll(fk=1)) pm.setParent(win+"matchFrameLayout") pm.setParent(win+"mainColumnLayout") pm.separator(h=5) pm.text('Release 3.1 Monika Gelbmann 11/2023') pm.separator(h=5) def inputSelTfb(self, name): if len(pm.selected()) == 0: pm.textFieldButtonGrp(win+name, e=1, tx='') return [] pm.textFieldButtonGrp(win+name, e=1, tx=pm.selected()[0]) def getAndCheckInputWin(self): inputValues = [] errorFields = [] # switch 0 is radio switch0isfkTfb= pm.radioCollection(win+"switch0isfkTfb", q=1, sl=1) if switch0isfkTfb == 'ikfkswitchUI_attr0IsFk': _logger.info( 'FK switch0isfk is %s'%switch0isfkTfb ) switch0isfk = 1 else: _logger.info('IK switch0isfk is %s'%switch0isfkTfb ) switch0isfk = 0 # switch range radio switchAttrRangeTfb = pm.radioCollection(win+"switchAttrRangeTfb", q=1, sl=1) if switchAttrRangeTfb == 'ikfkswitchUI_attr1': switchAttrRange = 1 else: switchAttrRange = 10 # check empty input text fields for inputTxtFldBtnGrp in self.inputTxtFldBtnGrps: input = pm.textFieldButtonGrp(inputTxtFldBtnGrp, q=1, tx=1) if len(input) == 0: errorFields.append(pm.textFieldButtonGrp(inputTxtFldBtnGrp, q=1, buttonLabel=1)) if len(errorFields) > 0: message = 'Empty input field found. Please pick Ctrl to use.\n%s'%errorFields self.popupWarning(message) pm.error(message) return False # check ctrls are valid and do exist for inputTxtFldBtnGrp in self.inputTxtFldBtnGrps[:-1]: input = pm.textFieldButtonGrp(inputTxtFldBtnGrp, q=1, tx=1) if pm.objExists(input) == 0: errorFields.append(pm.textFieldButtonGrp(inputTxtFldBtnGrp, q=1, buttonLabel=1)) else: inputValues.append(input) if len(errorFields) > 0: message = 'Non existing ctrls found. Check those names are correct:\n%s'%errorFields self.popupWarning(message) pm.error(message) return False # check switch attribute ctrlInput = pm.textFieldButtonGrp(self.inputTxtFldBtnGrps[-2], q=1, tx=1) attrInput = pm.textFieldButtonGrp(self.inputTxtFldBtnGrps[-1], q=1, tx=1) attr = '%s.%s'%(ctrlInput, attrInput) if pm.objExists(attr) == False: message = 'Switch Attribute does not exist. Check the naming:\n%s'%attr pm.warning(message) self.popupWarning(message) pm.error(message) return False else: inputValues.append(attrInput) # limb radio box limbRadio = pm.radioCollection(win+"limbRadioCollt", q=1, sl=1) _logger.info('raidobuttons: %s' % limbRadio) if limbRadio == 'NONE': message = 'Limb choice missing. Please choose R Arm / L Arm / R Leg / L Leg' self.popupWarning(message=message) pm.warning(message) return False ###TODO IK PIV can stay empty... ###TODO how to align with fk if there is no pv in ik # if pm.objExists(ikpv) == 0: # pm.error('Input Piv %s does not exist. Aborting'%input) # return False # validate offset numeric input fields rotOffsetX = pm.textField(win+'rotOffsetX', q=1, tx=1) _logger.debug('checking offsets') try: rotOffsetX = float(rotOffsetX) except: rotOffsetX = 0.0 pass rotOffsetY = pm.textField(win+'rotOffsetY', q=1, tx=1) try: rotOffsetY = float(rotOffsetY) except: rotOffsetY = 0.0 pass rotOffsetZ = pm.textField(win+'rotOffsetZ', q=1, tx=1) try: rotOffsetZ = float(rotOffsetZ) except: rotOffsetZ = 0.0 pass rotOffset=[rotOffsetX, rotOffsetY, rotOffsetZ] bendKneeAxis = pm.radioCollection(win+"bendKneeAxisTfb", q=1, sl=1) if bendKneeAxis == 'ikfkswitchUI_pX': bendKneeAxis = '+X' elif bendKneeAxis == 'ikfkswitchUI_nX': bendKneeAxis = '-X' elif bendKneeAxis == 'ikfkswitchUI_pY': bendKneeAxis = '+Y' elif bendKneeAxis == 'ikfkswitchUI_nY': bendKneeAxis = '-Y' elif bendKneeAxis == 'ikfkswitchUI_pZ': bendKneeAxis = '+Z' elif bendKneeAxis == 'ikfkswitchUI_nZ': bendKneeAxis = '-Z' inputValues.append(switch0isfk) inputValues.append(switchAttrRange) inputValues.append(rotOffset) inputValues.append(bendKneeAxis) _logger.info('returning %s'%inputValues) return inputValues def clearInputFields(self): # query text input fields for inputTxtFldBtnGrp in self.inputTxtFldBtnGrps: pm.textFieldButtonGrp(inputTxtFldBtnGrp, e=1, tx='') for inputTxtFld in self.inputTxtFlds: pm.textField(inputTxtFld, e=1, tx='') def popupWarning(self, message, title='Input Error'): result = pm.confirmDialog( title=title, message=message, button=['OK'], defaultButton='OK',) return result def autoDetectSideAndLimbWin(self): side, limb = autoDetectSideAndLimb(pm.selected()[0]) if side and limb: pm.displayInfo( 'Matching Side and Limb found: %s %s'%(side, limb)) if side == 'R' and limb == 'arm': pm.radioCollection(win+'limbRadioCollt' , edit=1, select=self.rb1) elif side == 'L' and limb == 'arm': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb2) elif side == 'R' and limb == 'leg': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb3) elif side == 'L' and limb == 'leg': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb4) self.loadIkFkCtrlsWin() def inputChannelboxSelectionTbf(self, name): channelBox = pm.mel.eval('global string $gChannelBoxName; $temp=$gChannelBoxName;') #fetch maya's main channelbox attrs = pm.channelBox(channelBox, q=True, sma=True) if not attrs: pm.textFieldButtonGrp(win+name, e=1, tx='') return [] if len(attrs) != 1: pm.warning('Highlight only the IK/FK Switch Attribute in the Channelbox') return [] pm.textFieldButtonGrp(win+name, e=1, tx=attrs[0]) return attrs def findStoreNodeFromSelectionWin(self): store_node = findStoreNodeFromSelection() _logger.debug( 'store node found is %s'%store_node) if store_node == []: message = 'No Storenode found for Selection. Fill out Setup section first and hit SAVE for future detection' pm.warning(message) #self.popupWarning(message) pm.text(self.readyText, e=1, label='No Storenode found. Use Setup. Not Ready.', align='left', bgc=(.6,.4,.4)) else: ns = store_node.split('__')[0] if len(store_node.split('__'))>0 else '' side = store_node.split('_')[-3] limb = store_node.split('_')[-2] pm.displayInfo( 'Storenode found for %s %s. Loading %s'%(side, limb, store_node)) if side and limb: _logger.info( 'Machting Side and Limb found') if side == 'R' and limb == 'arm': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb1) elif side == 'L' and limb == 'arm': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb2) elif side == 'R' and limb == 'leg': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb3) elif side == 'L' and limb == 'leg': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb4) self.loadIkFkCtrlsWin() pm.text(self.readyText, e=1, label='Storenode found >> %s %s. Ready.'%(side,limb), align='left', bgc=(.4,.6,.4)) def saveIkFkCtrlsWin(self): fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis = self.getAndCheckInputWin() limbRadio = pm.radioCollection(win+"limbRadioCollt", q=1, sl=1) if limbRadio == 'NONE': pm.warning('Limb choice missing. Please choose form the UI options') return False limb = limbRadio.split('_')[-1] side = limbRadio.split('_')[1] storeNode = saveIKFkCtrls(limb, side, fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis) if storeNode: pm.displayInfo( 'Successfully saved.') #self.popupWarning(message = 'Successfully saved.', title='Storenode saved') def loadIkFkCtrlsWin(self): limbRadio = pm.radioCollection(win+"limbRadioCollt", q=1, sl=1) if limbRadio == 'NONE': message = 'Limb choice missing. Please choose R Arm / L Arm / R Leg / L Leg' self.popupWarning(message=message) pm.warning(message) return False limb = limbRadio.split('_')[-1] # limbRadio = ikfkswitchUI_R_arm side = limbRadio.split('_')[1] if len(pm.selected()) == 0: pm.warning('Select anything from the rigg') return False ns = pm.selected()[0].split(':')[0] if len(pm.selected()[0].split(':')) > 1 else '' storedic = loadIkFkCtrl(ns, limb, side) if len(storedic) == 0: pm.warning('No Store Node for %s. Define Limbs and Save Store Node'%limb) else: pm.displayInfo( 'Found Store Node for %s. Loading.'%limb ) for attrName, value in list(storedic.items()): if attrName == 'switch0isfk': _logger.info('load switch0isfk is %s'%value ) if value == '0': pm.radioCollection(win+"switch0isfkTfb", e=1, select=win+'attr0IsIk') else: pm.radioCollection(win+"switch0isfkTfb", e=1, select=win+'attr0IsFk') elif attrName == 'attrRange': _logger.info('load attrRange value is %s'%value ) if value == '1': pm.radioCollection(win+"switchAttrRangeTfb", e=1, select=win+'attr1') else: pm.radioCollection(win+"switchAttrRangeTfb", e=1, select=win+'attr10') elif attrName == 'rotOffset': rotList = eval(value) _logger.debug( 'rotation list eval is %rotList'%rotList) pm.textField(win+"rotOffsetX", e=1, tx=rotList[0]) pm.textField(win+"rotOffsetY", e=1, tx=rotList[1]) pm.textField(win+"rotOffsetZ", e=1, tx=rotList[2]) elif attrName == 'side': continue elif attrName == 'bendKneeAxis': _logger.info('load bendKneeAxis value is %s'%value ) if value == '+X': pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'pX') elif value == '-X': pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'nX') elif value == '+Y': pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'pY') elif value == '-Y': pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'nY') elif value == '+Z': pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'pZ') elif value == '-Z': pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'nZ') else: pm.textFieldButtonGrp(win+"%sTfb"%attrName, e=1, tx=value) def matchIkFkWin(self, tofk=1): try: fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis = self.getAndCheckInputWin() except: pm.warning( 'input error matchIkFkWin' ) return rotOffsetX = pm.textField(win+'rotOffsetX', q=1, tx=1) _logger.debug('rotOffset X is %s'%rotOffsetX ) if rotOffsetX == '' : rotOffsetX = 0.0 else: rotOffsetX = float(rotOffsetX) rotOffsetY = pm.textField(win+'rotOffsetY', q=1, tx=1) if rotOffsetY == '' : rotOffsetY = 0.0 else: rotOffsetY = float(rotOffsetY) rotOffsetZ = pm.textField(win+'rotOffsetZ', q=1, tx=1) if rotOffsetZ == '' : rotOffsetZ = 0.0 else: rotOffsetZ = float(rotOffsetZ) limbRadio = pm.radioCollection(win+"limbRadioCollt", q=1, sl=1) side = limbRadio.split('_')[1] limb = limbRadio.split('_')[2] if tofk == 1: ikfkMatch(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=switch0isfk, switchAttrRange=switchAttrRange, rotOffset=[rotOffsetX, rotOffsetY, rotOffsetZ], side=side, limb=limb, bendKneeAxis=bendKneeAxis) elif tofk == 0: fkikMatch(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=switch0isfk, switchAttrRange=switchAttrRange, rotOffset=[rotOffsetX, rotOffsetY, rotOffsetZ], side=side, limb=limb) pm.select(switchCtrl) def switchIkFkWin(self): fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, kneeBendAxis = self.getAndCheckInputWin() if switch0isfk == 1: setSwitchTo = switchAttrRange else: setSwitchTo = 0 pm.setAttr('%s.%s'%(switchCtrl, switchAttr), setSwitchTo) pm.displayInfo( 'Done. Switched IK >> FK') def switchFkIkWin(self): fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, kneeBendAxis = self.getAndCheckInputWin() if switch0isfk == 1: setSwitchTo = 0 else: setSwitchTo = switchAttrRange pm.setAttr('%s.%s'%(switchCtrl, switchAttr), setSwitchTo) pm.displayInfo( 'Done. Switched FK >> IK') def selectAll(self, fk=1): try: fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, kneeBendAxis = self.getAndCheckInputWin() except: pm.warning('Input error selectAll') return if fk == 1: pm.select(fkshldr, fkellbow, fkwrist) pm.displayInfo('Done. Select 3 fk Ctrls.') elif fk == 0: pm.select(ikpv,ikwrist) pm.displayInfo('Done. Select 2 ik Ctrls.') return def keyAll(self, fk=1): try: fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis = self.getAndCheckInputWin() except: pm.warning('Input error keyAll') return if fk == 1: pm.setKeyframe(fkwrist,s=0, rk=1, mr=1) pm.setKeyframe(fkellbow,s=0, rk=1, mr=1) pm.setKeyframe(fkshldr,s=0, rk=1, mr=1) pm.displayInfo('Done. Set Keyframes on 3 fk Ctrls.') elif fk == 0: pm.setKeyframe(ikpv,s=0, rk=1, mr=1) pm.setKeyframe(ikwrist,s=0, rk=1, mr=1) pm.displayInfo('Done. Set Keyframes on 2 ik Ctrls.') return def findStoreNodeFromSelection(): selection = pm.selected()[0] namespace = selection.split(':')[0] + ':' if len(selection.split(':')) > 1 else '' referenced_node = False storenode_namespace = namespace.replace(':', '__') character_storenodes = pm.ls('%s*_IKFKSTORE'%storenode_namespace) if character_storenodes == []: # referenced store node EDIT 23.11.23 character_storenodes = pm.ls('%s*_IKFKSTORE'%namespace) referenced_node == True if character_storenodes == []: return [] for storenode in character_storenodes: storedic = {'fkwrist': '', 'fkellbow': '', 'fkshldr': '', 'ikwrist': '', 'ikpv': '', 'switchCtrl': ''} for attrName, value in list(storedic.items()): _logger.debug(storenode.attr(attrName).get() + ' selection is ' + selection.name()) if referenced_node: # referenced store node EDIT 23.11.23 storedic[attrName] = namespace+storenode.attr(attrName).get() else: storedic[attrName] = storenode.attr(attrName).get() if (selection.name() == storedic[attrName]) or (selection.name() == namespace+storedic[attrName]): _logger.debug( 'Found selection in store node %s'%storedic[attrName] ) return storenode return [] def autoDetectSideAndLimb(ctrl=None): ''' Need to have one ctrl selecte. This ctrl will determine side, namespace and suffix From there we list all matching nodes and try to find Limb From there we filter FK IK Returns: ''' if ctrl == None: ctrl = pm.selected()[0] ctrlname = pm.PyNode(ctrl).nodeName() namespace = ctrl.split(':')[0]+':' if len(ctrl.split(':')) > 1 else '' suffix = '_' + ctrlname.split('_')[-1] if 'ctrl' in ctrlname.split('_')[-1] else '' side = None limb = None # Detect Side for search_str in ['rt', 'Rt', 'R_', '_R', 'r_', '_r', 'right']: if search_str in ctrlname: side, side_str = 'R', search_str break if side == None: for search_str in ['lf', 'Lf', 'L_', '_L', 'l_', '_l', 'left']: if search_str in ctrlname: side, side_str = 'L', search_str break #_logger.debug('Side found: %s from %s' % (side, side_str)) # Detect Limb #side_ctrls = pm.ls('%s*%s*fk*%s' % (namespace, side_str, suffix),exactType='transform') #for side_ctrl in side_ctrls: # side_ctrlname = side_ctrl.nodeName().split(namespace)[-1].split(suffix)[0] for search_str in ['hand', 'Hand', 'arm', 'Arm', 'elbow', 'ellbow', 'Elbow', 'wrist', 'Wrist']: if search_str in ctrlname: #_logger.debug(' Arm detected %s'%ctrlname) limb = 'arm' break if limb == None: for search_str in ['leg', 'Leg', 'knee', 'Knee', 'foot', 'Foot']: if search_str in ctrlname: #_logger.debug(' Leg detected %s' % ctrlname ) limb = 'leg' break return side, limb def fkikMatch(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=1, switchAttrRange=1, rotOffset=[0,0,0], side='R', limb='arm'): ''' Match fk to ik. Recreate the ik chain Args: fkwrist: fkellbow: fkshldr: ikwrist: ikpv: switchCtrl: switchAttr: switch0isfk: rotOffset: Returns: ''' switch = '%s.%s'%(switchCtrl, switchAttr) if pm.objExists('snapGrp'): pm.delete('snapGrp') snapGrp = pm.createNode('transform', name='snapGrp') clist=[] # dup controls to constrain fk_wristDup = pm.duplicate(fkwrist, parentOnly=1, n='fk_wristDup')[0] unlockAttributes([fk_wristDup]) pm.parent(fk_wristDup, snapGrp) # go to fk mode to match correct position if switch0isfk == 0: pm.setAttr(switch, switchAttrRange) # 0 is fk else: pm.setAttr(switch, 0) # store fk keyframes on attribute or not: fkwrist_key, fkellbow_key, fkshldr_key = pm.keyframe(fkwrist, q=1, t=pm.currentTime()),\ pm.keyframe(fkellbow, q=1, t=pm.currentTime()),\ pm.keyframe(fkshldr, q=1, t=pm.currentTime()) # get positions from fk fkwRaw = pm.xform(fkwrist, ws=1, q=1, t=1) fkwPos = om.MVector(fkwRaw[0], fkwRaw[1], fkwRaw[2]) fkeRaw = pm.xform(fkellbow, ws=1, q=1, t=1) fkePos = om.MVector(fkeRaw[0], fkeRaw[1], fkeRaw[2]) fksRaw = pm.xform(fkshldr, ws=1, q=1, t=1) fksPos = om.MVector(fksRaw[0], fksRaw[1], fksRaw[2]) # store rotation fkwRotRaw = pm.xform(fkwrist, q=1, ro=1) fkeRotRaw = pm.xform(fkellbow, q=1, ro=1) fksRotRaw = pm.xform(fkshldr, q=1, ro=1) # zero out fk pm.xform(fkshldr, ro=(0,0,0)) pm.xform(fkellbow, ro=(0,0,0)) pm.xform(fkwrist, ro=(0,0,0)) snap(fkwrist, fk_wristDup) # create orig ik wrist dup to get offset pm.xform(ikwrist, ro=(0,0,0)) ik_wristDup = pm.duplicate(ikwrist, parentOnly=1, n='ik_wristDup')[0] unlockAttributes([ik_wristDup]) pm.parent(ik_wristDup, fk_wristDup) snap(fk_wristDup, ik_wristDup, pos=1, rot=1) #snap(ikwrist, ik_wristDup, pos=0, rot=1) ik_wristDupOffset = pm.duplicate(ik_wristDup, parentOnly=1, n='ik_wristDup_offset')[0] pm.parent(ik_wristDupOffset, ik_wristDup) clist.append(pm.parentConstraint(fkwrist,fk_wristDup, mo=0)) # restore fk pm.xform(fkshldr, ro=fksRotRaw) pm.xform(fkellbow, ro=fkeRotRaw) pm.xform(fkwrist, ro=fkwRotRaw) #considering rotation offset pm.setAttr('%s.rx'%ik_wristDupOffset, rotOffset[0]) pm.setAttr('%s.ry'%ik_wristDupOffset, rotOffset[1]) pm.setAttr('%s.rz'%ik_wristDupOffset, rotOffset[2]) # pole vector fkshldr_dup = pm.spaceLocator(n='fkShld_dup') snap(fkshldr, fkshldr_dup) pm.parent(fkshldr_dup, snapGrp) fkellbow_dup = pm.spaceLocator(n='fkEllbow_dup') snap(fkellbow, fkellbow_dup) pm.parent(fkellbow_dup, snapGrp) fkwrist_dup = pm.spaceLocator(n='fkwrist_dup') snap(fkwrist, fkwrist_dup) pm.parent(fkwrist_dup, snapGrp) pvLoc = poleVectorPosition(fkshldr_dup, fkellbow_dup, fkwrist_dup, length=12, createLoc =1) pm.select([fkshldr, fkellbow, fkwrist]) pm.parent(pvLoc, snapGrp) # snap ik for ctrl in [ikwrist, ikpv]: if len(pm.keyframe(ctrl, q=1))>0: pm.cutKey(ctrl, t=pm.currentTime()) snap(ik_wristDupOffset, ikwrist) snap(pvLoc, ikpv, pos=1, rot=0) for ctrl in [ikwrist, ikpv]: if len(pm.keyframe(ctrl, q=1))>0: pm.setKeyframe(ctrl, t=pm.currentTime(), s=0) if debug == True: clist.append(pm.parentConstraint(ik_wristDupOffset, ikwrist)) # clean up if debug == False: pm.delete(clist) pm.delete(snapGrp) #pm.delete(pvLoc) #if not debug: pm.delete(fkRotLocWs) # clean up eventually created keyframe on fk ctrl on switch frame if len(fkwrist_key) == 0: try : pm.cutKey(fkwrist, t=pm.currentTime()) except: pass if len(fkellbow_key) == 0: try : pm.cutKey(fkellbow, t=pm.currentTime()) except: pass if len(fkshldr_key) == 0: try : pm.cutKey(fkshldr, t=pm.currentTime()) except: pass # go to ik mode if switch0isfk == 0: pm.setAttr(switch, 0) else: pm.setAttr(switch, switchAttrRange) pm.dgdirty([ikwrist, ikpv]) pm.dgdirty([fkwrist, fkellbow, fkshldr]) _logger.info( 'Done matching FK to IK.') def keyframeAll(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=1, rotOffset=[0,0,0]): for ctrl in [fkwrist, fkellbow, fkshldr, ikwrist, ikpv]: for attr in ['tx', 'ty', 'tz', 'rx', 'ry', 'rz']: try: pm.setKeyframe(ctrl, at=attr) except: pass pm.setKeyframe(switchCtrl, at=switchAttr) def ikfkMatch(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=1, switchAttrRange=1, rotOffset=[0,0,0], side='R', limb='arm', guessUp=1, bendKneeAxis='+X'): ''' Snap fk to ik controls by building ik joint form fk control position and lining up to ik Args: Returns: ''' ns = fkwrist.split(':')[0] switch = '%s.%s'%(switchCtrl, switchAttr) clist = [] if pm.objExists('snapGrp'): pm.delete('snapGrp') snapGrp = pm.createNode('transform', name='snapGrp') # store if keyframe on ik attribute or not: ikwrist_key, ikpv_key = pm.keyframe(ikwrist, q=1, t=pm.currentTime()),\ pm.keyframe(ikpv, q=1, t=pm.currentTime()) _logger.info( 'matching. switch attr range is %s'%switchAttrRange ) # go to fk mode to match correct position (some riggs use same foot ctrl for ik and fk) if switch0isfk == 0: pm.setAttr(switch, switchAttrRange) # 0 is fk else: pm.setAttr(switch, 0) # zero out fk pm.xform(fkshldr, ro=(0,0,0)) pm.xform(fkellbow, ro=(0,0,0)) pm.xform(fkwrist, ro=(0,0,0)) try : pm.xform(fkshldr, t=(0,0,0)) except:pass try : pm.xform(fkellbow, t=(0,0,0)) except:pass try : pm.xform(fkwrist, t=(0,0,0)) except:pass _logger.info('root loc') pm.dgdirty([fkshldr, fkellbow, fkwrist]) root_loc = pm.group(empty=1, n='fk_shld_root') pm.parent(root_loc, snapGrp) snap(fkshldr, root_loc) fkshldr_dup = pm.duplicate(fkshldr, parentOnly=1)[0] fkellbow_dup = pm.duplicate(fkellbow, parentOnly=1)[0] fkwrist_dup = pm.duplicate(fkwrist, parentOnly=1)[0] #unlock all of duplicate A's arrtibutes basicTransforms = ['translateX','translateY','translateZ', 'translate', 'rotateX',' rotateY','rotateZ', 'rotate'] for attr in basicTransforms: #unlock attr pm.setAttr((fkshldr_dup + '.' + attr), lock=False, k=True) pm.setAttr((fkellbow_dup + '.' + attr), lock=False, k=True) pm.setAttr((fkwrist_dup + '.' + attr), lock=False, k=True) pm.select([fkshldr_dup, fkellbow_dup, fkwrist_dup]) _logger.info('line up fk duplicates to fk controlssss %s %s %s'%(fkshldr_dup, fkellbow_dup, fkwrist_dup)) # line up fk duplicates to fk controls pm.parent(fkshldr_dup, snapGrp) snap(fkshldr, fkshldr_dup, pos=1, rot=1) pm.parent(fkellbow_dup,fkshldr_dup) snap(fkellbow, fkellbow_dup, pos=1, rot=1) pm.parent(fkwrist_dup, fkellbow_dup) snap(fkwrist, fkwrist_dup, pos=1, rot=1) pm.select(snapGrp) _logger.info('snapping fk shoulder to ik') root_ikSnap = pm.joint(n='root_ikSnap', p=pm.xform(fkshldr, t=1, q=1, ws=1), orientation=(0, 0, 0)) pm.parent(root_ikSnap, root_loc) snap(fkshldr, root_ikSnap, rot=1, pos=1) ikshldr_jnt = pm.joint(n='ikshldr_jnt', p=pm.xform(fkshldr, t=1, q=1, ws=1), orientation=(0, 0, 0)) snap(fkellbow, ikshldr_jnt, rot=1, pos=0) try: snap(fkshldr, ikshldr_jnt, rot=0, pos=1) except: pass _logger.info('snapping fk ellbow to ik') ikellbow_jnt = pm.joint(n='ikellbow_jnt', p=pm.xform(fkellbow, t=1, q=1, ws=1), orientation=(0, 0, 0)) snap(fkellbow, ikellbow_jnt, rot=1, pos=0) try: snap(fkellbow, ikellbow_jnt, rot=0, pos=1) except: pass _logger.info('snapping fk wrist to ik') ikwrist_jnt = pm.joint(n='ikwrist_jnt', p=pm.xform(fkwrist, t=1, q=1, ws=1), orientation=(0, 0, 0)) snap(fkellbow, ikwrist_jnt, rot=1, pos=0) try: snap(fkwrist, ikwrist_jnt, rot=0, pos=1) except: pass #aimaxis = max(pm.getAttr('%s.tx'%ikellbow_jnt), pm.getAttr('%s.tx'%ikellbow_jnt), pm.getAttr('%s.tx'%ikellbow_jnt)) _logger.info('freeze transform') pm.makeIdentity(ikshldr_jnt, apply=1) pm.makeIdentity(ikellbow_jnt, apply=1) pm.makeIdentity(ikwrist_jnt, apply=1) multiplyer = 1 if bendKneeAxis[0] == '-': mutliplyer = -1 if abs(pm.getAttr('%s.jointOrient%s'%(ikellbow_jnt, bendKneeAxis[1]))) < 0.1: pm.warning('Warning small joint orient. Setting Prefferec Angle to Y ' ) pm.setAttr('%s.preferredAngle%s'%(ikellbow_jnt, bendKneeAxis[1]), 12.0*multiplyer) pm.setAttr('%s.jointOrient%s'%(ikellbow_jnt, bendKneeAxis[1]), 0.01*multiplyer) # pole vector pole_ikSnap = pm.spaceLocator(n='pole_ikSnap') pm.parent(pole_ikSnap, fkellbow_dup) _logger.info('snap pole ik to fkellbow knee bend axis is %s'%bendKneeAxis) # temp pole vector position. use the ellbow could use poleVectorPos as well snap(fkellbow_dup, pole_ikSnap) _logger.info('considering kneebendaxis. %s'%bendKneeAxis) reverse = 1 if side == 'L': reverse = -1 if bendKneeAxis == '-X': pole_ikSnap.tz.set(pole_ikSnap.tz.get()+0.5*reverse) elif bendKneeAxis == '+X': pole_ikSnap.tz.set(pole_ikSnap.tz.get()-0.5*reverse) elif bendKneeAxis == '-Y': pole_ikSnap.tz.set(pole_ikSnap.tz.get()+0.5*reverse) elif bendKneeAxis == '+Y': pole_ikSnap.tz.set(pole_ikSnap.tx.get()-0.5*reverse) elif bendKneeAxis == '-Z': pole_ikSnap.ty.set(pole_ikSnap.ty.get()-0.5*reverse) elif bendKneeAxis == '+Z': pole_ikSnap.ty.set(pole_ikSnap.ty.get()+0.5*reverse) pm.parent(pole_ikSnap, snapGrp) # ik handle ikHandle_ikSnap = pm.ikHandle(sj=ikshldr_jnt, ee=ikwrist_jnt, sol='ikRPsolver') pm.parent(ikHandle_ikSnap[0], snapGrp) pm.poleVectorConstraint(pole_ikSnap, ikHandle_ikSnap[0]) _logger.info( 'done polevector constraint' ) # wrist offset locator line up to zero out ikwrist ikrot = pm.xform(ikwrist, q=1, ro=1) pm.xform(ikwrist, ro=(0,0,0)) ikwrist_loc = pm.spaceLocator(n='ikwrist_loc') pm.setAttr('%s.rotateOrder'%ikwrist_loc, pm.getAttr('%s.rotateOrder'%ikwrist)) pm.parent(ikwrist_loc, fkwrist_dup) snap(fkwrist, ikwrist_loc, rot=0, pos=1) snap(fkwrist, ikwrist_loc, rot=1, pos=0) ikwrist_loc_offset = pm.spaceLocator(n='ikwrist_loc_offset') pm.setAttr('%s.rotateOrder'%ikwrist_loc_offset, pm.getAttr('%s.rotateOrder'%ikwrist)) pm.parent(ikwrist_loc_offset, ikwrist_loc) snap(ikwrist_jnt, ikwrist_loc_offset, rot=0, pos=1) snap(fkwrist, ikwrist_loc_offset, rot=1, pos=0) # considering rotation offset (reverse) _logger.info( 'considering rotation offset' ) fkwrist_rotOrder = pm.getAttr('%s.rotateOrder'%fkwrist) ikwrist_rotOrder = pm.getAttr('%s.rotateOrder'%ikwrist) _logger.debug('rotation order ikwrist: %s. fkwrist: %s'%(fkwrist_rotOrder,ikwrist_rotOrder)) pm.setAttr('%s.rx'%ikwrist_loc_offset, rotOffset[0] ) pm.setAttr('%s.ry'%ikwrist_loc_offset, rotOffset[1] ) pm.setAttr('%s.rz'%ikwrist_loc_offset, rotOffset[2] ) # constrain fk ctrl dups to ikSnap locs _logger.info( 'constrain fk ctrl dups to ikSnap locs' ) clist.append(pm.parentConstraint(ikshldr_jnt, fkshldr_dup, skipTranslate = ['x', 'y', 'z'], mo=1) ) clist.append(pm.parentConstraint(ikellbow_jnt, fkellbow_dup, skipTranslate = ['x', 'y', 'z'], mo=1) ) clist.append(pm.parentConstraint(ikwrist_jnt, fkwrist_dup, mo=1) ) fkwrist_loc = pm.spaceLocator(n='fkwrist_loc') pm.setAttr('%s.rotateOrder'%fkwrist_loc, pm.getAttr('%s.rotateOrder'%fkwrist)) pm.parent(fkwrist_loc, ikwrist_loc_offset) snap(fkwrist, fkwrist_loc) pm.setAttr('%s.rx'%ikwrist_loc_offset,0) pm.setAttr('%s.ry'%ikwrist_loc_offset, 0) pm.setAttr('%s.rz'%ikwrist_loc_offset, 0) # rotate back ik _logger.info( 'rotate back ik' ) pm.xform(ikwrist, ro=ikrot) clist.append(pm.parentConstraint(ikwrist, ikwrist_loc, mo=0) ) if debugZero: return # switch to ik mode (some riggs use same foot ctrl for ik and fk) if switch0isfk == 0: pm.setAttr(switch, 0) # 0 is fk else: pm.setAttr(switch, switchAttrRange) # line up to ik wrist and pole _logger.info( 'line up to ik wrist and pole' ) clist.append(pm.pointConstraint(ikwrist, ikHandle_ikSnap[0])) snap(ikpv, pole_ikSnap, rot=0, pos=1) # get wrist rotation #snap(ikwrist, fkwrist_loc, rot=1, pos=0) # snap(fkshldr_loc, fkshldr, rot=1, pos=0) # snap(fkellbow_loc, fkellbow, rot=1, pos=0) # snap(fkwrist_loc, fkwrist, rot=1, pos=0) _logger.debug('snapping back to original fk') # snap back to original fk ctlrs for ctrl in [fkshldr, fkellbow, fkwrist]: if len(pm.keyframe(ctrl, q=1))>0: pm.cutKey(ctrl, t=pm.currentTime()) _logger.info( 'snap fk shoulder' ) snap(fkshldr_dup, fkshldr, rot=1, pos=0) try: snap(fkshldr_dup, fkshldr, pos=1) except: pass _logger.info( 'snap fk ellbow' ) snap(fkellbow_dup, fkellbow, rot=1, pos=0) try: snap(fkellbow_dup, fkellbow, pos=1) except: pass _logger.info( 'snap fk wrist' ) snap(fkwrist_loc, fkwrist, rot=1, pos=0) try: snap(fkwrist_loc, fkwrist, pos=1) except: pass for ctrl in [fkshldr, fkellbow, fkwrist]: if len(pm.keyframe(ctrl, q=1))>0: pm.setKeyframe(ctrl, t=pm.currentTime(), s=0) pm.dgdirty([fkshldr, fkellbow, fkwrist]) # debug mode if debug == True: pm.parentConstraint(fkwrist_loc, fkwrist, mo=0, st=('x', 'y', 'z')) # clean up if debug == False: pm.delete(clist) pm.delete(snapGrp) # clean up eventually created keyframe on ik ctrl on switch frame if len(ikwrist_key) == 0: try : pm.cutKey(ikwrist, t=pm.currentTime()) except: pass if len(ikpv_key) == 0: try : pm.cutKey(ikpv, t=pm.currentTime()) except: pass # set to ik if switch0isfk == 0: pm.setAttr(switch, 1) else: pm.setAttr(switch, 0) def saveIKFkCtrls(limb, side, fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis): ''' limb = 'arm'/'leg side = 'R'/'L' ''' sel = pm.selected() ns = fkwrist.split(':')[0] if len(fkwrist.split(':')) > 1 else '' storenode = ns + '__' + side + '_' + limb + '_IKFKSTORE' _logger.info('Storenode is %s'%storenode) if pm.objExists(storenode) == False: storenode = pm.createNode('transform', n=storenode) else: message = 'Do you want to replace existing store node?' confirm = pm.confirmDialog( title='Replace existing', message=message, button=['Yes','No'], defaultButton='Yes', cancelButton='No', dismissString='No' ) if confirm == 'Yes': _logger.info('deleting existing store node') pm.delete(storenode) storenode = pm.createNode('transform', n=storenode) else: pm.select(sel) return storenode = pm.PyNode(storenode) storedic = {'fkwrist': fkwrist, 'fkellbow': fkellbow, 'fkshldr':fkshldr, 'ikwrist':ikwrist, 'ikpv':ikpv, 'switchCtrl':switchCtrl, 'switchAttr':switchAttr, 'switch0isfk':switch0isfk, 'attrRange':switchAttrRange, 'rotOffset':rotOffset, 'side':side, 'bendKneeAxis':bendKneeAxis} for attrName, value in list(storedic.items()): pm.addAttr(storenode, ln=attrName, dt='string', k=1) storenode.attr(attrName).set('%s'%value) pm.select(sel) return storenode def loadIkFkCtrl(ns, limb, side): ''' limb = 'arm'/'leg side = 'R'/'L' ''' storenodeRegex = ns + '__' + side + '_' + limb + '_IKFKSTORE' _logger.info('loading %s '%storenodeRegex) storenode = pm.ls(storenodeRegex) storenode_referenced = False if len(storenode) == 0: # referenced store node EDIT 23.11.23 storenodeRegex = ns + ':__' + side + '_' + limb + '_IKFKSTORE' storenode = pm.ls(storenodeRegex) storenode_referenced = True if len(storenode) == 0: #_logger.info( 'No storenode found' ) return {} else: storenode = storenode[0] ns = storenode.split('__')[0] storenode = ns + '__' + side + '_' + limb + '_IKFKSTORE' if pm.objExists(storenode) == False: return {} storenode = pm.PyNode(storenode) storedic = {'fkwrist': '', 'fkellbow': '', 'fkshldr':'', 'ikwrist':'', 'ikpv':'', 'switchCtrl':'', 'switchAttr':'', 'switch0isfk':'', 'attrRange':'', 'rotOffset':'', 'bendKneeAxis':'+X'} i=0 for attrName, value in list(storedic.items()): try: if storenode_referenced and i<6: # referenced store node EDIT 23.11.23 storedic[attrName] = ns+storenode.attr(attrName).get() else: storedic[attrName] = storenode.attr(attrName).get() except: pm.warning('Missing Attribute %s. Please Save Store Node again.'%attrName) storedic[attrName] = value i=i+1 _logger.info('StoreNode found is %s'%storedic) return storedic def get_variable_name(var_value, main_var): mvar = [key for key, val in list(main_var.items()) if val==var_value][0] _logger.info( 'var: %s >> %s'%(mvar, var_value)) # 123 {'test_var': 123} test_var return [mvar, var_value] def matchTransform(slave, master, rot=1, pos=1): ''' Mimicking innate matchTransform of maya 2016.5 and up Args: slave: this object will be moved to master master: target position and rotation ''' if rot == 0: skipRotAxis=["x","y","z"] else: skipRotAxis = [] if pos == 0: skipTransAxis=["x","y","z"] else: skipTransAxis = [] if rot == 1: target = pm.xform(master, q=1, ro=1, ws=1) pm.xform(slave, ro=target, ws=1) if pos == 1: target = pm.xform(master, q=1, t=1, ws=1) pm.xform(slave, t=target, ws=1) # Align with Parent Constrain def snap(master=None, slave=None, pos=1, rot=1): ''' Snap slave to master. Check if attribute locked and skip ''' lastSel = pm.selected() if master == None: master = pm.selected()[0] if slave == None: slave = pm.selected()[1:] slaves = pm.ls(slave) ptC, ptR = [], [] # for each slave, parentconstrain for each position and rotation, skipping locked attributes for slave in slaves: slaveDup = pm.duplicate(slave, parentOnly=True)[0] _logger.debug('snapping slaveDup') #unlock all of duplicate A's arrtibutes basicTransforms = ['translateX','translateY','translateZ', 'translate','rotateX','rotateY','rotateZ','rotate'] for attr in basicTransforms: #unlock attr pm.setAttr((slaveDup + '.' + attr), lock=False, k=1) ptC=pm.parentConstraint(master, slaveDup, mo=False) if pos == 1: for att in ['tx', 'ty', 'tz']: if pm.getAttr('%s.%s'%(slave,att), l=1) == False: pm.setAttr((slave + '.' + att), pm.getAttr((slaveDup + '.' + att))) _logger.info('Snap Constraining Traslation %s %s. Skiplist is '%(master, slave) ) if rot == 1: for att in ['rx', 'ry', 'rz']: if pm.getAttr('%s.%s'%(slave,att), l=1) == False: pm.setAttr((slave + '.' + att), pm.getAttr((slaveDup + '.' + att))) _logger.info('Snap Constraining Rotation %s %s. Skiplist is '%(master, slave)) pm.delete(ptC) pm.delete(slaveDup) pm.select(lastSel) def poleVectorPosition(startJnt, midJnt, endJnt, length=12, createLoc =0): import maya.api.OpenMaya as om start = pm.xform(startJnt ,q= 1 ,ws = 1,t =1 ) mid = pm.xform(midJnt ,q= 1 ,ws = 1,t =1 ) end = pm.xform(endJnt ,q= 1 ,ws = 1,t =1 ) startV = om.MVector(start[0] ,start[1],start[2]) midV = om.MVector(mid[0] ,mid[1],mid[2]) endV = om.MVector(end[0] ,end[1],end[2]) startEnd = endV - startV startMid = midV - startV # projection vector is vecA projected onto vecB # it is calculated by dot product if one vector normalized # proj= vecA * vecB.normalized (dot product result is scalar) proj = startMid * startEnd.normal() # multiply proj scalar with normalized startEndVector to project it onto vector startEndN = startEnd.normal() projV = startEndN * proj arrowV = startMid - projV arrowVN = arrowV.normal() # scale up to length and offset to midV finalV = arrowVN*length + midV if createLoc: loc = pm.spaceLocator(n='polePos') pm.xform(loc , ws =1 , t= (finalV.x , finalV.y ,finalV.z)) return loc return finalV def unlockAttributes(objects, attributes=['translateX','translateY','translateZ','rotateX',' rotateY','rotateZ', 'visibility']): #unlock all of duplicate A's arrtibutes for obj in objects: for attr in attributes: #unlock attr pm.setAttr((obj + '.' + attr), lock=False, k=True) pm.setAttr((obj + '.' + attr), lock=False, k=True) pm.setAttr((obj + '.' + attr), lock=False, k=True) if attr == 'visibility': pm.setAttr((obj + '.' + attr), 1) def orientJoints(joints, aimAxis, upAxis, upDir, doAuto): """ * * $joints is array of joints to orient * $aimAxis = is xyz array of what axis of joint does aim * $upAxis = is xyz array of what axis of joint does up * $upDir = what vector to use for up direction? * $doAuto = If possible will try to guess the up axis otherwise * it will use prev joint up axis or else world upDir. * """ nJnt=len(joints) i = 0 prevUp=pm.dt.Vector([0, 0, 0]) # Now orient each joint for i in range(0,nJnt): childs=pm.listRelatives(joints[i], type=["transform", "joint"], children=1) # First we need to unparent everything and then store that, if len(childs)>0: childs=pm.parent(childs, w=1) # unparent and get NEW names in case they changed... # Find parent for later in case we need it. parents=pm.listRelatives(joints[i], parent=1) parent=parents[0] # Now if we have a child joint...aim to that. aimTgt="" child = "" for child in childs: if pm.nodeType(child) == "joint": aimTgt=str(child) break if aimTgt != "": upVec=[0, 0, 0] # First off...if $doAuto is on, we need to guess the cross axis dir. # if doAuto: posJ=pm.xform(joints[i], q=1, rp=1, ws=1) # Now since the first joint we want to match the second orientation # we kind of hack the things passed in if it is the first joint # ie: If the joint doesn't have a parent...OR if the parent it has # has the "same" position as itself...then we use the "next" joints # as the up cross calculations # posP=posJ if parent != "": posP=pm.xform(parent, q=1, rp=1, ws=1) tol=0.0001 # How close to we consider "same"? if parent == "" or (abs(posJ[0] - posP[0])<=tol and abs(posJ[1] - posP[1])<=tol and abs(posJ[2] - posP[2])<=tol): aimChilds=pm.listRelatives(aimTgt, children=1) aimChild="" child = "" for child in aimChilds: if pm.nodeType(child) == "joint": aimChild=str(child) break upVec=pm.mel.cJO_getCrossDir(joints[i], aimTgt, aimChild) else: upVec=pm.mel.cJO_getCrossDir(parent, joints[i], aimTgt) if not doAuto or (upVec[0] == 0.0 and upVec[1] == 0.0 and upVec[2] == 0.0): upVec=upDir # or else use user set up Dir. if needed aCons=pm.aimConstraint(aimTgt, joints[i], aim=(aimAxis[0], aimAxis[1], aimAxis[2]), worldUpType="vector", weight=1.0, upVector=(upAxis[0], upAxis[1], upAxis[2]), worldUpVector=(upVec[0], upVec[1], upVec[2])) pm.delete(aCons) # Now compare the up we used to the prev one. curUp=pm.dt.Vector([upVec[0], upVec[1], upVec[2]]) curUp=pm.dt.Vector(pm.mel.unit(curUp)) dot=float(curUp * prevUp) # dot product for angle betwen... prevUp=pm.dt.Vector([upVec[0], upVec[1], upVec[2]]) # store for later if i>0 and dot<=0.0: pm.xform(joints[i], r=1, os=1, ra=((aimAxis[0] * 180.0), (aimAxis[1] * 180.0), (aimAxis[2] * 180.0))) # Adjust the rotation axis 180 if it looks like we've flopped the wrong way! prevUp*=pm.dt.Vector(-1.0) pm.joint(joints[i], zso=1, e=1) # And now finish clearing out joint axis... pm.makeIdentity(joints[i], apply=True) elif parent != "": oCons=pm.orientConstraint(parent, joints[i], weight=1.0) # Otherwise if there is no target, just dup orienation of parent... pm.delete(oCons) # And now finish clearing out joint axis... pm.joint(joints[i], zso=1, e=1) pm.makeIdentity(joints[i], apply=True) if len(childs)>0: pm.parent(childs, joints[i]) # Now that we're done... reparent