Search code examples
pythonmaya

Maya Python: Connect three Joint chains via Tripple List


I've been trying to build an autorigging script of my own when it comes to IKFK limbs based on the Antony Ward Method here: https://www.youtube.com/watch?v=uzHn_4ByyjY&t=10s

So far it's coming along really nicely: but I've run into a speedbump. How this script works is simple:

  1. Select a joint chain with any number of joints and hit "Rename Joint Chain"

  2. Set the side prefix and name prefix of the limb you want to build in the text fields, for example: textfield 1: "Left" textfield 2: "Leg"

  3. The Inc From end slider determines the placement of the IKFK toggle control on the joint chain. Zero puts it at the end, every increment up slides it up the joint chain.

  4. Select the root of your joint chain and Hit "Build Template"

Where the script hiccups is step 5. I tried creating a triple list to parent constrain the BIND joint chain between the IK and FK joint chain: but I keep getting the error

"# Error: TypeError: file d:/Users/Name/Documents/maya/2020/scripts\DS_03C_autoHumanArm.py line 217: can only concatenate list (not "str") to list #"

I need it to parent constrain the start joint, end joint, and however many joints are inbetween to their respective IK and FK duplicates, then connect the reverse node's outputX to the generated parent constraint's w1 attribute and the generated IKFK toggle's IK_Toggle to the the generated parent constraint's w0. The problems occurs around line 201:

In terms of what I've tried so far it's mostly been a varient if the double list zip technique detailed here: Maya: Connect two Joint chains with Parent Constraint but I dont know how to adapt it for more than two joint chains

'''
import DS_03C_autoHumanArm
reload (DS_03C_autoHumanArm)
DS_03C_autoHumanArm.gui()
'''

import maya.cmds as cmds
if cmds.window("moduleWin", exists =True):
    cmds.deleteUI("moduleWin", window = True)

myWindow = cmds.window("moduleWin",t='DS_joint_renamerV1',rtf=1,w=100, h=100, toolbox=True)
column = cmds.columnLayout(adj=True)

def gui():  
    #cmds.button(label='Print Instructions(Check Script Editor)',c=printInstructions)

    cmds.rowLayout(numberOfColumns = 3,adjustableColumn=2)
    cmds.textField('prefixText',it = 'lf',editable=True)
    cmds.textField('limbText',it='arm',editable=True)
    cmds.setParent('..')

    cmds.button( label="Rename Joint Chain",c=DS_module_rename)
    cmds.separator(h=9)

    cmds.columnLayout(adj=True)
    cmds.optionMenu('controlShape', label='Control Shape')
    cmds.menuItem( label='cog' )
    cmds.menuItem( label='cube' )
    cmds.menuItem( label='circle' )
    cmds.setParent('..')

    cmds.intSliderGrp('incDwnChain',
        label='Inc From End',
        min=0,
        max=10,
        value=1,
        field=True,
        columnWidth=[(1,80),(2,40),(3,150)])

    cmds.colorIndexSliderGrp('controlColor',
        label='Control Color',
        min=0,
        max=31,
        value=1,
        columnWidth=[(1,80),(2,40),(3,150)])

    cmds.button( label="Build Template",c=buildTemplate)
    cmds.separator(h=9)

    cmds.button( label="Finalise Limb",c=finaliseArm)
    cmds.separator(h=9)

    cmds.setParent('..')
    cmds.showWindow(myWindow)

def DS_module_rename(*args):
    namePref = cmds.textField('prefixText', query=True, text=True)
    limbPref = cmds.textField('limbText', query=True, text=True)
    ctlName = namePref+'_'+limbPref

    #list all joints in chain, this list will be refrenced by all the commands beneath it
    root = cmds.ls(sl=True)[0]
    child = cmds.listRelatives(root,ad=1,type='joint')
    limbJnt = child

    #rename the arm joints
    for j, name in enumerate(child):
        cmds.rename(name,ctlName + 'AJ{0}_BIND_JNT'.format(len(child)-j))
        print(child)

    root = cmds.ls(sl=True)[0]
    child = cmds.listRelatives(root,ad=1,f=True,children=True,type='joint')
    cmds.rename(child[0],ctlName +'AJ_BIND_END_JNT')
    cmds.rename(root,ctlName +'AJ_BIND_START_JNT')

def buildTemplate(*args):
    namePref = cmds.textField('prefixText', query=True, text=True)
    limbPref = cmds.textField('limbText', query=True, text=True)
    ctlName = namePref+'_'+limbPref

    shapePref = cmds.optionMenu('controlShape',query=True,value=True)
    incPref = cmds.intSliderGrp('incDwnChain',query=True,value=True)
    colorPref = cmds.colorIndexSliderGrp('controlColor',query=True,value=True) #this variable links to the color slider group in the GUI
    colorPref = colorPref -1 # this reverses the first variable to ensure you are getting the proper color from the color selector

    root = cmds.ls(sl=True)[0] # adding a zero bracket makes sure it counts the head of the herarchy too
    #root = cmds.ls(sl=True)[-1] # the brackets negative one gets the end of a list
    child = cmds.listRelatives(root,ad=1,type='joint')
    limbJnt = child

    ctl = ctlName+'_ikfk_toggle'

    if shapePref == 'cog':
        cmds.circle(n=ctl,nr = (90,0,0))
    elif shapePref == 'cube':
        cmds.circle(n=ctl,nr = (90,0,0))
    elif shapePref == 'circle':
        cmds.circle(n=ctl,nr = (90,0,0))

    cmds.addAttr(ctl,
        ln='IK_Toggle',
        defaultValue=1.0,
        minValue=0,
        maxValue=1,
        attributeType='float', 
        keyable=True)
    cmds.addAttr(ctl,
        ln='CONTROLS',
        at="enum",
        en="---------------:")
    cmds.setAttr(ctl + '.CONTROLS',
        k=True,
        lock=True)
    cmds.addAttr(ctl,
        ln='rotateAll',
        attributeType='float', 
        keyable=True)
    cmds.addAttr(ctl,
        ln='Spread',
        attributeType='float', 
        keyable=True)
    cmds.addAttr(ctl,
        ln='Scratch',
        attributeType='float', 
        keyable=True)

    cmds.group(n=ctlName+ 'AttrOrient_GRP')

    #forLoop/list increment to last of joint chain to place the IKFK toggle
    cmds.select(ctlName+'AJ1_BIND_JNT')

    #snap ikfk toggle to proper point on the joint chain without parent constraint
    baseCon = cmds.parentConstraint(child[incPref],ctlName+'AttrOrient_GRP',mo=False)

    #set color of control
    cmds.setAttr(ctl + '.overrideEnabled', 1)
    cmds.setAttr(ctl + '.overrideColor', colorPref)#create a textfield for manually enterring the number of the color

    cmds.createNode('reverse',n= ctlName + '_reverseNode')

    cmds.connectAttr(ctl + '.IK_Toggle', ctlName + '_reverseNode.inputX' )

    #list all FK joints in chain
    fkChain = cmds.duplicate(ctlName +'AJ_BIND_START_JNT', n= ctlName +'AJ_FK_START_JNT')
    fkList = cmds.listRelatives(ctlName +'AJ_FK_START_JNT', ad=True,pa=True)
    for fkn, name in enumerate(fkList):
        cmds.rename(name, ctlName +'AJ{0}_FK_JNT'.format(len(fkList)-fkn))

        #select FK chain, then,set joints size for easy grabbing on FK chain
        cmds.select(ctlName +'AJ_FK_START_JNT')

        fkRoot = cmds.ls(sl=True)[0] 
        fkChild = cmds.listRelatives(ctlName +'AJ_FK_START_JNT', ad=True,pa=True)
        fkChild.append(fkRoot)

        for r in fkChild:
            cmds.setAttr(r + '.radius', 2.0)

    ikChain = cmds.duplicate(ctlName +'AJ_BIND_START_JNT', n= ctlName +'AJ_IK_START_JNT')
    ikList = cmds.listRelatives(ctlName +'AJ_IK_START_JNT', ad=True,pa=True)
    for fkn, name in enumerate(ikList):
        cmds.rename(name, ctlName +'AJ{0}_FK_JNT'.format(len(ikList)-fkn))

        #select FK chain, then,set joints size for easy grabbing on FK chain
        cmds.select(ctlName +'AJ_IK_START_JNT')

        ikRoot = cmds.ls(sl=True)[0] 
        ikChild = cmds.listRelatives(ctlName +'AJ_IK_START_JNT', ad=True,pa=True)
        ikChild.append(ikRoot)

        for r in ikChild:
            cmds.setAttr(r + '.radius', 2.5)

def finaliseArm(*args):
    namePref = cmds.textField('prefixText', query=True, text=True)
    limbPref = cmds.textField('limbText', query=True, text=True)
    ctlName = namePref+'_'+limbPref
    ctl = namePref+limbPref+'_ikfk_toggle'

    ikJntChain=cmds.listRelatives(ctlName +'AJ_IK_START_JNT',ad=1,type='joint')
    ikJntChain.append(ctlName +'AJ_IK_START_JNT')
    ikJntChain.reverse()
    ikLimbJnt = ikJntChain

    print(ikJntChain)

    FKJntChain=cmds.listRelatives(ctlName +'AJ_FK_START_JNT',ad=1,type='joint')
    FKJntChain.append(ctlName +'AJ_IK_START_JNT')
    FKJntChain.reverse()
    FKLimbJnt = FKJntChain

    print(FKJntChain)

    boundJntChain=cmds.listRelatives(ctlName +'AJ_BIND_START_JNT',ad=1,type='joint')
    boundJntChain.append(ctlName +'AJ_BIND_START_JNT')
    boundJntChain.reverse()
    boundLimbJnt = boundJntChain

    print(boundJntChain)

    for ik_chain,fk_chain,bound_chain in zip(ikJntChain,FKJntChain,boundJntChain):
        spineCons = cmds.parentConstraint(ik_chain,fk_chain,bound_chain,mo=False)
        cmds.connectAttr(ctlName + '_reverseNode.outputX',spineCons+'.w1')
        cmds.connectAttr(ctl + '.IK_Toggle',spineCons+'.w0')

thank you for your help


Solution

  • The error that you're getting is complaining about that you're trying to concatenate a list with a string, which Python cannot do. Specifically, spineCons is the list, and instead you can simply access its first index like this spineCons[0].

    But there's more issues once you fix that. One is that you're trying to concatenate '.w1' to connect to the parent constraint's weight attribute, but that's not the attribute's right name. You can get its attribute names by calling cmds.parentConstraint(spineCons[0], q=True, weightAliasList=True).

    Another issue is that you have 2 places where you forgot to change IK to FK, which was causing 2 FK chains being built in the template!

    And finally, the last issue is at the very end when you're trying to connect ctl + '.IK_Toggle', because that object doesn't have that attribute which exists. I didn't dig too much in this one but to fix it you just need to pass the proper object.

    Here's the script with the fixes. You can do a search on FIXME to see where I left comments:

    '''
    import DS_03C_autoHumanArm
    reload (DS_03C_autoHumanArm)
    DS_03C_autoHumanArm.gui()
    '''
    
    import maya.cmds as cmds
    if cmds.window("moduleWin", exists =True):
        cmds.deleteUI("moduleWin", window = True)
    
    myWindow = cmds.window("moduleWin",t='DS_joint_renamerV1',rtf=1,w=100, h=100, toolbox=True)
    column = cmds.columnLayout(adj=True)
    
    def gui():  
        #cmds.button(label='Print Instructions(Check Script Editor)',c=printInstructions)
    
        cmds.rowLayout(numberOfColumns = 3,adjustableColumn=2)
        cmds.textField('prefixText',it = 'lf',editable=True)
        cmds.textField('limbText',it='arm',editable=True)
        cmds.setParent('..')
    
        cmds.button( label="Rename Joint Chain",c=DS_module_rename)
        cmds.separator(h=9)
    
        cmds.columnLayout(adj=True)
        cmds.optionMenu('controlShape', label='Control Shape')
        cmds.menuItem( label='cog' )
        cmds.menuItem( label='cube' )
        cmds.menuItem( label='circle' )
        cmds.setParent('..')
    
        cmds.intSliderGrp('incDwnChain',
            label='Inc From End',
            min=0,
            max=10,
            value=1,
            field=True,
            columnWidth=[(1,80),(2,40),(3,150)])
    
        cmds.colorIndexSliderGrp('controlColor',
            label='Control Color',
            min=0,
            max=31,
            value=1,
            columnWidth=[(1,80),(2,40),(3,150)])
    
        cmds.button( label="Build Template",c=buildTemplate)
        cmds.separator(h=9)
    
        cmds.button( label="Finalise Limb",c=finaliseArm)
        cmds.separator(h=9)
    
        cmds.setParent('..')
        cmds.showWindow(myWindow)
    
    def DS_module_rename(*args):
        namePref = cmds.textField('prefixText', query=True, text=True)
        limbPref = cmds.textField('limbText', query=True, text=True)
        ctlName = namePref+'_'+limbPref
    
        #list all joints in chain, this list will be refrenced by all the commands beneath it
        root = cmds.ls(sl=True)[0]
        child = cmds.listRelatives(root,ad=1,type='joint')
        limbJnt = child
    
        #rename the arm joints
        for j, name in enumerate(child):
            cmds.rename(name,ctlName + 'AJ{0}_BIND_JNT'.format(len(child)-j))
            print(child)
    
        root = cmds.ls(sl=True)[0]
        child = cmds.listRelatives(root,ad=1,f=True,children=True,type='joint')
        cmds.rename(child[0],ctlName +'AJ_BIND_END_JNT')
        cmds.rename(root,ctlName +'AJ_BIND_START_JNT')
    
    def buildTemplate(*args):
        namePref = cmds.textField('prefixText', query=True, text=True)
        limbPref = cmds.textField('limbText', query=True, text=True)
        ctlName = namePref+'_'+limbPref
    
        shapePref = cmds.optionMenu('controlShape',query=True,value=True)
        incPref = cmds.intSliderGrp('incDwnChain',query=True,value=True)
        colorPref = cmds.colorIndexSliderGrp('controlColor',query=True,value=True) #this variable links to the color slider group in the GUI
        colorPref = colorPref -1 # this reverses the first variable to ensure you are getting the proper color from the color selector
    
        root = cmds.ls(sl=True)[0] # adding a zero bracket makes sure it counts the head of the herarchy too
        #root = cmds.ls(sl=True)[-1] # the brackets negative one gets the end of a list
        child = cmds.listRelatives(root,ad=1,type='joint')
        limbJnt = child
    
        ctl = ctlName+'_ikfk_toggle'
    
        if shapePref == 'cog':
            cmds.circle(n=ctl,nr = (90,0,0))
        elif shapePref == 'cube':
            cmds.circle(n=ctl,nr = (90,0,0))
        elif shapePref == 'circle':
            cmds.circle(n=ctl,nr = (90,0,0))
    
        cmds.addAttr(ctl,
            ln='IK_Toggle',
            defaultValue=1.0,
            minValue=0,
            maxValue=1,
            attributeType='float', 
            keyable=True)
        cmds.addAttr(ctl,
            ln='CONTROLS',
            at="enum",
            en="---------------:")
        cmds.setAttr(ctl + '.CONTROLS',
            k=True,
            lock=True)
        cmds.addAttr(ctl,
            ln='rotateAll',
            attributeType='float', 
            keyable=True)
        cmds.addAttr(ctl,
            ln='Spread',
            attributeType='float', 
            keyable=True)
        cmds.addAttr(ctl,
            ln='Scratch',
            attributeType='float', 
            keyable=True)
    
        cmds.group(n=ctlName+ 'AttrOrient_GRP')
    
        #forLoop/list increment to last of joint chain to place the IKFK toggle
        cmds.select(ctlName+'AJ1_BIND_JNT')
    
        #snap ikfk toggle to proper point on the joint chain without parent constraint
        baseCon = cmds.parentConstraint(child[incPref],ctlName+'AttrOrient_GRP',mo=False)
    
        #set color of control
        cmds.setAttr(ctl + '.overrideEnabled', 1)
        cmds.setAttr(ctl + '.overrideColor', colorPref)#create a textfield for manually enterring the number of the color
    
        cmds.createNode('reverse',n= ctlName + '_reverseNode')
    
        cmds.connectAttr(ctl + '.IK_Toggle', ctlName + '_reverseNode.inputX' )
    
        #list all FK joints in chain
        fkChain = cmds.duplicate(ctlName +'AJ_BIND_START_JNT', n= ctlName +'AJ_FK_START_JNT')
        fkList = cmds.listRelatives(ctlName +'AJ_FK_START_JNT', ad=True,pa=True)
        for fkn, name in enumerate(fkList):
            cmds.rename(name, ctlName +'AJ{0}_FK_JNT'.format(len(fkList)-fkn))
    
            #select FK chain, then,set joints size for easy grabbing on FK chain
            cmds.select(ctlName +'AJ_FK_START_JNT')
    
            fkRoot = cmds.ls(sl=True)[0] 
            fkChild = cmds.listRelatives(ctlName +'AJ_FK_START_JNT', ad=True,pa=True)
            fkChild.append(fkRoot)
    
            for r in fkChild:
                cmds.setAttr(r + '.radius', 2.0)
    
        ikChain = cmds.duplicate(ctlName +'AJ_BIND_START_JNT', n= ctlName +'AJ_IK_START_JNT')
        ikList = cmds.listRelatives(ctlName +'AJ_IK_START_JNT', ad=True,pa=True)
        for fkn, name in enumerate(ikList):
            cmds.rename(name, ctlName +'AJ{0}_IK_JNT'.format(len(ikList)-fkn))  # FIXME: This should be ik!
    
            #select FK chain, then,set joints size for easy grabbing on FK chain
            cmds.select(ctlName +'AJ_IK_START_JNT')
    
            ikRoot = cmds.ls(sl=True)[0] 
            ikChild = cmds.listRelatives(ctlName +'AJ_IK_START_JNT', ad=True,pa=True)
            ikChild.append(ikRoot)
    
            for r in ikChild:
                cmds.setAttr(r + '.radius', 2.5)
    
    def finaliseArm(*args):
        namePref = cmds.textField('prefixText', query=True, text=True)
        limbPref = cmds.textField('limbText', query=True, text=True)
        ctlName = namePref+'_'+limbPref
        ctl = namePref+limbPref+'_ikfk_toggle'
    
        ikJntChain=cmds.listRelatives(ctlName +'AJ_IK_START_JNT',ad=1,type='joint')
        ikJntChain.append(ctlName +'AJ_IK_START_JNT')
        ikJntChain.reverse()
        ikLimbJnt = ikJntChain
    
        print(ikJntChain)
    
        FKJntChain=cmds.listRelatives(ctlName +'AJ_FK_START_JNT',ad=1,type='joint')
        FKJntChain.append(ctlName +'AJ_FK_START_JNT')  # FIXME: This was using IK before!
        FKJntChain.reverse()
        FKLimbJnt = FKJntChain
    
        print(FKJntChain)
    
        boundJntChain=cmds.listRelatives(ctlName +'AJ_BIND_START_JNT',ad=1,type='joint')
        boundJntChain.append(ctlName +'AJ_BIND_START_JNT')
        boundJntChain.reverse()
        boundLimbJnt = boundJntChain
    
        print(boundJntChain)
    
        for ik_chain,fk_chain,bound_chain in zip(ikJntChain,FKJntChain,boundJntChain):
            spineCons = cmds.parentConstraint(ik_chain,fk_chain,bound_chain,mo=False)
            weightAttrs = cmds.parentConstraint(spineCons[0], q=True, weightAliasList=True)  # FIXME: Get constraint weight attribute names.
            cmds.connectAttr(ctlName + '_reverseNode.outputX',spineCons[0] + '.' + weightAttrs[1])  # FIXME: `spineCons` is a list, so make sure to access its first index.
            cmds.connectAttr(ctl + '.IK_Toggle',spineCons[0] + '.' + weightAttrs[0])  # FIXME: This will error because `IK_Toggle` doesn't exist