IK / FK Matching

* Copy the script below to the clipboard and paste it into Maya/Script Editor to be able to execute.

import maya.cmds as cmds

DefaultSwitchKeyword='Switch, switch, SWITCH'
DefaultIKWord='IK'
DefaultFKWord='FK'
DefaultLimitValue = 0.0

#ik/fk matcher script
#for our 2 functions, the expectation is that uset has selected an IK/FK switch control
#how can we figure out by selecting the switch control to know the joints
#we can validate the switch control selection by checking it for our switch custom attribute
def joint_selection(switch_option):
    #get the current switch option menu selection
    switch = cmds.optionMenu(switch_option, q = True, value =True)
    #get the parent of the switch control
    parent_grp = cmds.listRelatives(switch, parent=True)
    #get the children of the parent 
    children_grp = cmds.listRelatives(parent_grp, ad=True, fullPath=True)
    found=False#bool attribute for while loop
    count = 0#count attribute for while loop
    joint_constraint = ''
    #if children exist:
    if children_grp:
        #while loop to check every item in the children list
        while count<len(children_grp) and found==False:
            #if the type of the item is parent constraint
            if cmds.nodeType(children_grp[count])=='parentConstraint':
                #stop looping
                found=True
                #assign the item to a variable
                joint_constraint=children_grp[count]
            count+=1#continue loop
        #if parent constraint exist:
        if joint_constraint != '':
            #get the short name of the joint constraint
            short_name = joint_constraint.split('|')[-1]
            #get the connected joint from the constraint
            connection=cmds.listConnections(short_name, type = 'joint', d=False, s=True)
            #remove repetitions
            connection = list(set(connection))
            #if connection is not empty:
            if connection:
                #get the wrist joint
                wrist_jnt = connection[0]
                #assign the parent of the wrist joint to the variable (elbow joint)
                parent_jnt = cmds.listRelatives(wrist_jnt, parent = True)
                #if the parent of the wrist joint exists:
                if parent_jnt:
                    #assign it to the variable elbow_jnt
                    elbow_jnt = parent_jnt[0]
                    #assign the parent of the elbow joint to the variable parent jnt2
                    parent_jnt2 = cmds.listRelatives(elbow_jnt, parent = True)
                    #if the parent of the elbow joint exists:
                    if parent_jnt2:
                        #assign it to the variable shoulder joint
                        shoulder_jnt = parent_jnt2[0]
                        joints = [shoulder_jnt, elbow_jnt, wrist_jnt] #create a variable for joints
                        return joints
                    else:
                        cmds.warning('Could not find proper joint hierarchy based on the constrained joint.')
                        return None
                else:
                    cmds.warning('Could not find proper joint hierarchy based on the constrained joint.')
                    return None
            else:
                cmds.warning('Could not find any joint constrained to the control.')#warning message
                #confirm dialog to let the user choose a joint
                joint_confirm = cmds.confirmDialog(title = 'Joint confirmation', message = 'Could not find any joint constrained to the control. If you would like to continue, please select the the wrist joint that the control affects.', button = ['Yes(with the joint selected)', 'No'])
                #if the user wants to continue
                if joint_confirm == 'Yes(with the joint selected)':
                    #get the selection
                    sel = cmds.ls(selection = True, type = 'joint')
                    #if user selected smt:
                    if sel:
                        #get the wrist joint
                        wrist_jnt = sel[0]
                        #assign the parent of the wrist joint to the variable (elbow joint)
                        parent_jnt = cmds.listRelatives(wrist_jnt, parent = True)
                        #if the parent of the wrist joint exists:
                        if parent_jnt:
                            #assign it to the variable elbow_jnt
                            elbow_jnt = parent_jnt[0]
                            #assign the parent of the elbow joint to the variable parent jnt2
                            parent_jnt2 = cmds.listRelatives(elbow_jnt, parent = True)
                            #if the parent of the elbow joint exists:
                            if parent_jnt2:
                                #assign it to the variable shoulder joint
                                shoulder_jnt = parent_jnt2[0]
                                joints = [shoulder_jnt, elbow_jnt, wrist_jnt] #create a variable for joints
                                return joints
                            else:
                                cmds.warning('Could not find proper joint hierarchy based on the selected joint.')
                                return None
                        else:
                            cmds.warning('Could not find proper joint hierarchy based on the selected joint.')
                            return None
                    else:
                        cmds.warning('Nothing selected.')
                        return None
                else:
                    return None
        else:
            cmds.warning('Could not find any parent constraint. Please check whether the control has a constraint.')
            return None
    else:
        cmds.warning('Could not find any parent or other objects under the same hierarchy.') 
        return None
 
def get_fk_controls(fk_joints):
    fk_controls = []#preset fk control list
    #for loop to go through every fk joint
    for jnt in fk_joints:
        #get the children of the joint
        children = cmds.listRelatives(jnt, fullPath = True, c=True)
        count = 0#count for while loop
        found = False#bool for while loop
        joint_constraint = ''
        #if children exist:
        if children:
            while count<len(children) and found==False:#when checking every child of the joint
                if cmds.nodeType(children[count])=='parentConstraint':#if the type of the child is parent constraint
                    found=True#stop while loop
                    joint_constraint=children[count]#assign it to the joint constraint var
                count+=1#continue counting
            #if joint constraint exists:
            if joint_constraint != '':
                #get the short name 
                short_name = joint_constraint.split('|')[-1]
                #get the connection
                connection=cmds.listConnections(short_name, d=False, s=True)
                connection = list(set(connection))#remove repetitions
                #for loop to go through every connection
                for each in connection:
                    #get the shape of the connection
                    child = cmds.listRelatives(each, fullPath = True, shapes = True)
                    #if shape exists:
                    if child:
                        if cmds.nodeType(child[0]) == 'nurbsCurve':#if the type of the shape is nurbcurve
                            fk_controls.insert(fk_joints.index(jnt), each)#add it to the corresponding fk control list
            else:
                cmds.warning('Could not find any constraint under {]'.format(jnt))
        else:
            cmds.warning('Could not find any children under {} '.format(jnt))
    if len(fk_controls)==len(fk_joints):#if find every fk control
        return fk_controls
    else:
        return None

def get_ik_controls(ik_joints):
    ik_controls = ['']#preset ik control list
    #get all the ik handles
    handles = cmds.ls(type = 'ikHandle')
    ik_handle = ''
    #for loop to go through every handle in the scene:
    for each in handles:
        #get the connection of the ik handle type joint
        connection=cmds.listConnections(each, d=False, s=True, type = 'joint')
        #check if the connection joint is one of the ik joint
        for jnt in ik_joints:
            if jnt == connection[0]:
                ik_handle = each
    #if there is an ik handle that is connected to the ik joint
    if ik_handle != '':
        #get the connetion of the ik handle
        handle_connection=cmds.listConnections(ik_handle, d=False, s=True)
        #remove repetitions
        handle_connection = list(set(handle_connection))
        #for loop to go through every connection
        for each in handle_connection:
            #if the type of the connection is a constraint
            if 'Constraint' in cmds.nodeType(each):
                #get the short name of the connection
                short_name = each.split('|')[-1]
                #get the connection of the handle connection
                connection=cmds.listConnections(short_name, d=False, s=True)
                connection = list(set(connection))#remove repetitions
                #check every connection
                for con in connection:
                    #get the shape of the connection
                    child = cmds.listRelatives(con, fullPath = True, shapes = True)
                    #if shape node exists:
                    if child:
                        if cmds.nodeType(child[0]) == 'nurbsCurve':#if the connection type is a nurbcurve
                            ik_controls.insert(1, con)     #add to the ik control list as elbow control
        children = cmds.listRelatives(ik_joints[2], fullPath = True, c=True)#get the children of the wrist joint
        count = 0#count for while loop
        found = False#bool for while loop
        joint_constraint = ''
        if children:#if children exist
            while count<len(children) and found==False:#when checking every child of the wrist joint
                if cmds.nodeType(children[count])=='parentConstraint':#if the type of the child is parent constraint
                    found=True#stop while loop
                    joint_constraint=children[count]#get the constraint
                count+=1
            if joint_constraint != '':#if parent constraint exist
                short_name = joint_constraint.split('|')[-1]#get the short name
                connection=cmds.listConnections(short_name, d=False, s=True)#get the connection of the short name
                connection = list(set(connection))#remove repetitions
                for each in connection:#for loop to go through every connection
                    child = cmds.listRelatives(each, fullPath = True, shapes = True)#get the shape of the connection
                    if child:#if shape node exists:
                        if cmds.nodeType(child[0]) == 'nurbsCurve':#if the type of the shape is nurbcurve
                            ik_controls.insert(2, each)#add the connection as wrist control
            else:
                cmds.warning('Could not find any constraint under the wrist joint.')
                return None
        else:
            cmds.warning('Could not find any children under the wrist joint.')
            return None
    else:
        cmds.warning('Could not find any ik handle connected to the joint of the selected switch control.')
        return None
    if len(ik_controls)==len(ik_joints):#if find every ik control:
        return ik_controls
    else:
        cmds.warning('Faild to evaluate the ik controls for the ik joints.')
        return None

def ik_mode(switch_option, attr, ik_val):
    #get the joints based on the switch selection
    joints = joint_selection(switch_option)
    #if joints exist:
    if joints != None:
        #get the forearm length between elbow and wrist
        forearm_length = cmds.getAttr('{}.translateX'.format(joints[2]))
        #get the upperarm length between shoulder and elbow
        upperarm_length = cmds.getAttr('{}.translateX'.format(joints[1]))
        #get the arm length
        arm_length = (forearm_length+upperarm_length)
        #get all the joints in the scene
        joint_all = cmds.ls(type = 'joint')
        fk_joints = []#predefine fk joint list
        ik_joints = []#predefine ik joint list
        #for loop to go trhough every joint
        for each in joint_all:
            #for loop to go through every joint of the control
            for jnt in joints:
                #if the joint contains fk keyword and the control joint name:
                if DefaultFKWord in each and jnt in each:
                    #add the joint to the fk joint list
                    fk_joints.insert(joints.index(jnt), each)
                #else if the name of the joint contains ik keyword and the control joint name:
                elif DefaultIKWord in each and jnt in each:
                    #add the joint to the ik joint list
                    ik_joints.insert(joints.index(jnt), each)
        if len(ik_joints)==len(joints) and len(fk_joints)==len(joints): #if find every fk joint and ik joint
            #get fk controls
            fk_controls = get_fk_controls(fk_joints)
            #get ik controls
            ik_controls = get_ik_controls(ik_joints)
            if fk_controls != None and ik_controls !=None:#if get the controls successfully
                #match translation and rotation of the wrist ik to the wrist fk
                cmds.matchTransform(ik_controls[2], fk_controls[2], pos=True, rot = True)
                #match translation and rotation of the elbow ik to the elbow fk
                cmds.matchTransform(ik_controls[1], fk_controls[1], pos = True, rot = True)
                #move the elbow control back
                if arm_length > 0:
                    cmds.move(0,0, -arm_length, ik_controls[1], relative = True)
                elif arm_length <0:
                    cmds.move(0,0, arm_length, ik_controls[1], relative = True)
                #get the ik value in the ik text field
                ikValue = cmds.textField(ik_val, q=True, text = True)
                #set the switch control to ik value
                switchAttr= cmds.optionMenu(attr,q=True, value = True)
                switch = cmds.optionMenu(switch_option, q = True, value =True)
                cmds.setAttr('{0}.{1}'.format(switch, switchAttr), float(ikValue))
        else:
            cmds.warning('Failed to find ik joints or fk joints. Please check the naming.')
    
        
def fk_mode(switch_option, attr, fk_val):
    #get the joints based on the switch selection
    joints = joint_selection(switch_option)
    #if joints exist:
    if joints != None:
        #get all the joints in the scene:
        joint_all = cmds.ls(type = 'joint')
        fk_joints = []#predefine fk joint list
        ik_joints = []#predefine ik joint list
        #for loop to go through every joint:
        for each in joint_all:
            #for loop to go through every joint of the control 
            for jnt in joints:
                #if the name of the joint contains fk keyword and the control joint name:
                if DefaultFKWord in each and jnt in each:
                    #add the joint to the fk joint list
                    fk_joints.insert(joints.index(jnt), each)
                #else if the name of the joint contains ik keyword and the control joint name:
                elif DefaultIKWord in each and jnt in each:
                    #add the joint to the ik joint list
                    ik_joints.insert(joints.index(jnt), each)
        if len(ik_joints)==len(joints) and len(fk_joints)==len(joints): #if find every fk joint and ik joint
            #get the fk controls based on the fk joints
            fk_controls = get_fk_controls(fk_joints)
            roAxis = ['X','Y','Z']#set the axis
            if fk_controls !=None:#if get fk_controls successfully
                #for loop to run 3 times
                for jnt in range(0,3):
                    #for loop to go through every axis
                    for ro in roAxis:
                        #get the value of the rotation of the ik joint
                        value = cmds.getAttr('{0}.rotate{1}'.format(ik_joints[jnt], ro))
                        #set the value of the ik joint to the fk joint
                        cmds.setAttr('{0}.rotate{1}'.format(fk_controls[jnt], ro), value)
                #get the fk value in the fk val text field
                fkValue = cmds.textField(fk_val, q=True, text = True)
                #set the switch attribute value to the fk value
                switchAttr= cmds.optionMenu(attr,q=True, value = True)
                switch= cmds.optionMenu(switch_option,q=True, value = True)
                cmds.setAttr('{0}.{1}'.format(switch, switchAttr), float(fkValue))
        else:
            cmds.warning('Failed to find ik joints or fk joints. Please check the naming.')
    
    
def change_attribute(switch, attr,ik_val, fk_val):
    #get the current attribtue selected in the attribute option menu
    attribute = cmds.optionMenu(attr, q=True, value = True)
    #get the switch option
    selection = cmds.optionMenu(switch, q = True, value =True)
    #get the limit of the selected attribute
    if cmds.attributeQuery(attribute, node=selection, minExists=True, maxExists=True):
        limit = cmds.attributeQuery(attribute, node=selection, range=True)
    else:
        limit = [DefaultLimitValue, DefaultLimitValue]
    #put the minimum to ik value
    cmds.textField(ik_val, text=limit[0],edit= True)
    #maximum to fk value
    cmds.textField(fk_val, text=limit[1],edit = True)
    
    
def change_switch(option, attr, ik_val, fk_val):
    #get the current switch option menu selection
    selection = cmds.optionMenu(option, q = True, value =True)
    #if selection exists:
    if selection:
        #get the user defined attributes in the selected switch controls
        switch_attr= cmds.listAttr(selection,ud=True)
        #get the attribute option menu items
        menuItems = cmds.optionMenu(attr, q = True, ill = True)
        #if there are things exist in the attribute option menu
        if menuItems != None and menuItems != []:
            cmds.deleteUI(menuItems)   #delete those items
        #if user defined attribute exists:
        if switch_attr: 
            # for loop to go through each attribute
            for at in switch_attr:
                cmds.menuItem(label=at, parent=attr)#add them into the attribute option menu
            #get the limit range of the first user defined attribute
            if cmds.attributeQuery(switch_attr[0], node=selection, minExists=True, maxExists=True):
                limit = cmds.attributeQuery(switch_attr[0], node=selection, range=True)
            else:
                limit = [DefaultLimitValue, DefaultLimitValue]
            #if ik_val and fk_val are not default values
            if ik_val != DefaultLimitValue and fk_val != DefaultLimitValue:
                #change the text field ik value to the minimum limit
                cmds.textField(ik_val, text=limit[0],edit= True)
                #change the text field fk value to the maximum limit
                cmds.textField(fk_val, text=limit[1],edit = True)
        else:
            cmds.warning('Could not find any user defined attributes in this control.')
    else:
        cmds.warning('Could not find any switch control containing the keyword. Please re-enter the keyword')

def search_switch(txt, option, attr, ik_val, fk_val):
    #get the items in the switch option menu and assign them in a var
    menuItems = cmds.optionMenu(option, q = True, ill = True)
    #if there are things exist in the switch option menu:
    if menuItems != None and menuItems != []:
        cmds.deleteUI(menuItems)#delete the items in the switch option menu
    #get the text from the keyword text field
    text = cmds.textField(txt, q=1, text=1)
    #split the keyword text
    key= text.split(',')
    #define the var for the keyword list
    keyword=[]
    #for loop to go through the text field keyword after splitting
    for each in key:
        #append each item in the new keyword list var
        keyword.append(each.strip())
    #get all the transforms in the scene
	sceneAll = cmds.ls(type = 'transform')
    #set a var for the switch list
    get_switch=[]
    #for loop to go through every transforms in the scene
    for item in sceneAll:        
        count=0 #set the count var for checking every keyword in the while loop
        found = False #bool for stopping the while loop
        #when checking every keyword and haven't found the item: 
        while count< len(keyword) and found==False:
            if keyword[count] in item: # if the item contains the keyword
                #get the shape of this item
                sp = cmds.listRelatives(item, shapes=True)
                #check if the type of the shape is a curve
                if sp:
                    if cmds.nodeType(item+'|'+sp[0]) == 'nurbsCurve':
                        get_switch.append(item)#append the item to the switch list
                        #set the found bool to true, stop the while loop
                        found=True
            count+=1#if cannot found anything with that keyword, check the next
    #for loop to go through each item in the switch list
    for each in get_switch:
        #add each item to the switch option menu
        cmds.menuItem( label=each, parent=option )
    #if the attribute is not empty: to automatically update the attribute option menu and value layout
    if attr != '':
        #change switch
        change_switch(option, attr, ik_val, fk_val)
    #if get switch is not an empty list:
    if get_switch:
        return get_switch[0] #return the first item of the switch list
    else:
        return None #else return none

def create_ui():
    script_ui = 'IK/FK Matcher' #The name of the UI
    ui_width = 400 #The width of the UI
    attr = '' #pre define the var for the attribute
    ik_val=DefaultLimitValue #pre define ik val
    fk_val= DefaultLimitValue# pre define fk val
    
    #if the window has already existed, then delete the one.
    if cmds.window(script_ui, exists = True):
            cmds.deleteUI(script_ui)

    #create the ui window
    wd = cmds.window(script_ui, width = ui_width, title = 'IK/FK Switch') 
    
    #The main layout (top of the hierarchy)
    main_layout = cmds.rowColumnLayout()
    
    #define description layout
    description_layout = cmds.rowColumnLayout(numberOfColumns = 1)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    #description texts
    cmds.text(label='1. Use keywords to find IK/FK Switch Control (use , to separate each keyword)', align = 'left')
    cmds.text(label = '2. Select the switch control and the switch attribute in the drop-down menu', align = 'left')   
    cmds.text(label = '3. Adjust the attribute values.', align = 'left')
    cmds.text(label = '4. Use IK Mode Matcher button or FK Mode Matcher button to match the pose', align = 'left')
    #separators
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    #parent it to the main layout
    cmds.setParent('..')
    
    #search layout
    searchWidth = [(1, ui_width*0.25), (2, ui_width*0.55), (3, ui_width*0.2)] #The width setting for the columns
    #define the layout
    search_layout = cmds.rowColumnLayout(numberOfColumns = 3, width = ui_width, columnWidth = searchWidth)
    #text to describe the text field
    cmds.text(label=' Switch Keywords: ', width = searchWidth[0][1])
    #text field for entering the keywords. by default it takes the var DefaultSwitchKeyword
    txt=cmds.textField(text=DefaultSwitchKeyword,width = searchWidth[1][1])
    #button to search by using the keywords.by pressing it goes to the function search_switch
    search_button= cmds.button(label='Search', width = searchWidth[2][1], command = lambda *args: search_switch(txt, switch_option, attr, ik_val, fk_val))
    cmds.separator(visible = False)#separator
    cmds.separator(visible = False)#separator
    cmds.separator(visible = False)#separator
    cmds.separator(visible = False)#separator
    #parent the layout
    cmds.setParent('..')
    
    #switch layout
    switchWidth=[(1, ui_width*0.25), (2, ui_width*0.75)]#switch layout width setting
    switch_layout = cmds.rowColumnLayout(numberOfColumns = 2, width = ui_width , columnWidth = switchWidth)#define the layout
    #describing text
    cmds.text(label=' IK/FK Switch:',width = switchWidth[0][1])
    #option menu for selecting the result of the search button. when changing the selection, it goes to the function change_switch
    switch_option= cmds.optionMenu( width = switchWidth[1][1], changeCommand=lambda *args:change_switch(switch_option, attr, ik_val, fk_val))
    #find the switch using the default value and assign it into a var
    default_switch=search_switch(txt, switch_option, attr, ik_val, fk_val)
    #separators
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    #descibing text for the attribute selection
    cmds.text(label=' Switch Attribute: ',width = switchWidth[0][1])
    #option menu for selecting the attribute. 
    attr=cmds.optionMenu(width = switchWidth[1][1], changeCommand = lambda *args:change_attribute(switch_option, attr,ik_val, fk_val))
    #if it finds a switch control by using the default value
    if default_switch != None:
        #find user defined attribute in the switch control
        default_attr = cmds.listAttr(default_switch,ud=True)
        #add the user defined attribute into the attribute option menu
        for at in default_attr:
            cmds.menuItem(label=at, parent=attr )
    #separators
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    #parent the layout
    cmds.setParent('..')
    
    #value layout
    value_width=[(1, ui_width*0.25), (2, ui_width*0.25), (3, ui_width*0.25), (4, ui_width*0.25)] 
    value_layout = cmds.rowColumnLayout(numberOfColumns = 4, width = ui_width)#define the layout
    #set the default ik value and fk value
    #if default switch exists by using the default keywords and default attribute has values:
    if default_switch != None and default_attr:
        #assign the attribute limit value(min+max)to the limit if the min and the max exist
        if cmds.attributeQuery(default_attr[0], node=default_switch, minExists=True, maxExists=True):
            defaultLimit = cmds.attributeQuery( default_attr[0], node=default_switch, range=True )
        else:#otherwise it still takes default value
            defaultLimit = [DefaultLimitValue, DefaultLimitValue]
    else:
        #otherwise the limit takes the default value
        defaultLimit = [DefaultLimitValue, DefaultLimitValue]
    #describing text for ik value
    cmds.text(label=' IK Value: ',width = value_width[0][1])
    #text field for ik value
    ik_val=cmds.textField(text=defaultLimit[0] ,width = value_width[1][1])
    #describing text for fk value
    cmds.text(label=' FK Value: ',width = value_width[2][1])
    #text field for fk value
    fk_val=cmds.textField(text=defaultLimit[1] ,width = value_width[3][1])
    #describing word for ik joints keyword(for finding the joints)
    cmds.text(label=' IK Joints Keyword: ',width = value_width[0][1])
    #text field for the ik keyword
    ik_key=cmds.textField(text=DefaultIKWord ,width = value_width[1][1])
    #describing word for fk joints keyword
    cmds.text(label=' FK Joints Keyword: ',width = value_width[2][1])
    #text field for the fk keyword
    fk_key=cmds.textField(text=DefaultFKWord ,width = value_width[3][1])
    cmds.separator(visible = False)    #separators
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    cmds.separator(visible = False)
    #parent the layout
    cmds.setParent('..')
    
    #button layout
    button_width=[(1, ui_width*0.5), (2, ui_width*0.5)] #the width setting for the layout
    #define the layout
    button_layout= cmds.rowColumnLayout(numberOfColumns = 2, width = ui_width, columnWidth = button_width)
    #button for ik mode
    ik_button=cmds.button(label='IK Mode Matcher',command = lambda *args: ik_mode(switch_option, attr, ik_val) )
    #button for fk mode
    fk_button=cmds.button(label='FK Mode Matcher',command = lambda *args: fk_mode(switch_option, attr, fk_val))
    #parent the layout
    cmds.setParent('..')
    
    #display the window
    cmds.showWindow(wd)
    
#call the window
create_ui()

© Jiuyang Wang │ 778-926-0288 │ einjw2999@gmail.com