Search code examples
pythonmaya

Maya: Connect two Joint chains with Parent Constraint


So here is a snipit of an IK spine builder I've been working on. I've figure out how to make lists to duplicate the bound into an IK chain, what I've got stuck on however is I want my list and for loop to parent constraint each joint in the bound hierarchy to it's corresponding joint in the ik hierarchy:

    import maya.cmds as cmds

def linkJointChain(lookFor='joint'):
    namePref = 'ct_'
    limbPref = 'spine'
    ctlName = namePref + limbPref

    #list selection to get the joint and their children
    root = cmds.ls(sl=True)[0] # adding a zero bracket makes sure it counts the head of the herarchy too
    child = cmds.listRelatives(root,ad=1,type='joint')
    child.append(root)
    child.reverse()
    limbJnt = child
    print(child)

    #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,f=True,children=True,type='joint')

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

    #rename beggining and end joints to start and end respectivly
    root = cmds.ls(sl=True)
    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')


    #duplicate bound chain for ik spine
    root = cmds.ls(sl=True)
    IKChain = cmds.duplicate(root,n=ctlName + 'AJ_IK_START_JNT')
    IKList = cmds.listRelatives(ctlName + 'AJ_IK_START_JNT', ad=True,pa=True)
    for IKn, name in enumerate(IKList):
        cmds.rename(name, ctlName +'AJ{0}_IK_JNT'.format(len(IKList)-IKn))
        print(IKList)

        #select IK chain, then,set joints size for easy grabbing on IK 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', 1.5)


    #parent constrain bound spine to ik spine
    ikJntChain=cmds.listRelatives(ctlName +'AJ_IK_START_JNT',ad=1,type='joint')
    ikJntChain.append(ctlName +'AJ_IK_START_JNT') #try appending your other joint chain to create a double list with which to append
    ikJntChain.reverse()
    ikLimbJnt = ikJntChain

    boundJntChain=cmds.listRelatives(ctlName +'AJ_BIND_START_JNT',ad=1,type='joint')
    boundJntChain.append(ctlName +'AJ_BIND_START_JNT') #try appending your other joint chain to create a double list with which to append
    boundJntChain.reverse()
    boundLimbJnt = boundJntChain

    limbJnt = ikJntChain+boundJntChain

    print(limbJnt)

    for j in limbJnt:
        spineCons = cmds.parentConstraint(ikJntChain[0],boundJntChain[0])
        #ikParChain = cmds.parentConstraint(j,ikJntChain)

linkJointChain()

the script has hardcoded names for the listRelatives because the full script reads the joint chain and places controls at the start and end joint after renaming the first and last joints in the list, I know it has something to do with the brackets in cmds.parentConstraint


Solution

  • Here's an example that will create 2 separate joint chains from scratch, then applies a parent constraint to each joint so that one chain drives the other:

    import maya.cmds as cmds
    
    joint_count = 10
    
    # Create 1st joint chain 'a'.
    chain_a = [
        cmds.joint(position=[0, i * -2 + ((joint_count - 1) * 2), 0], name="a#")
        for i in range(joint_count)]
    
    cmds.select(clear=True)  # Need to clear selection so the next chain doesn't accidentally parent to chain a.
    
    # Create 2nd joint chain 'b'.
    chain_b = [
        cmds.joint(position=[0, i * -2 + ((joint_count - 1) * 2), -10], name="b#")
        for i in range(joint_count)]
    
    # Use `zip` to iterate through both lists at the same time.
    for jnt_a, jnt_b in zip(chain_a, chain_b):
        cmds.parentConstraint(jnt_a, jnt_b, maintainOffset=True)  # Constraint b->a
    

    The main idea is that you get 2 lists each with their own joints. You then pass those 2 lists to zip, so that when you iterate through it, it will first go through both 1st joints, then both 2nd joints, and so on.

    To get this to work correctly you must make sure both lists have the same length, and both are using the same joint order. That way you don't have to hard-code anything and instead can do it procedurally (for example you can change joint_count to whatever number and it will still work).

    You actually don't even need to use zip and can achieve the same thing by replacing the ending like this:

    for i in range(len(chain_a)):
        cmds.parentConstraint(chain_a[i], chain_b[i], maintainOffset=True)  # Constraint b->a
    

    Though using zip feels more 'pythonic'.