MetaBox/Scripts/Animation/mog_ikFkSwitchFree.py

1480 lines
53 KiB
Python
Raw Permalink Normal View History

2025-01-14 03:07:03 +08:00
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