Stretchy Arm Script

 Stretchy Arm Automating Script

For this assignment I followed the tutorial on how to manually set up this stretchy arm and then I followed the tips to get started video. The second video walks through setting up the joints into groups so that you can more easily work with the three joints of the arm. 

The video also goes through a script on how to rename the joints. 

Functions 

find_and_rename_joints()

import maya.cmds as cmds
import string

def find_and_rename_joints(bodyPart, side): # "arm", "leg", etc., "Lf" or "Rt"

    letters = list(string.ascii_uppercase)
    
    joints = []
    childJoints = []
    
    joints.append(cmds.ls(sl=1) [0])
    
    childJoints = cmds.listRelatives(ad=1, type="joint")
    childJoints.reverse()
    
    for i in range(2):
        joints.append(childJoints[i])
    
    armJoints = []
    
    for i,joint in enumerate(joints):
        armJoint = cmds.rename(joint, bodyPart + "J" + letters[i] + side + "_1")
        armJoints.append(armJoint)
        
    if bodyPart == "arm":
            
        shoulderJoint = armJoints[0]
        elbowJoint = armJoints[1]
        wristJoint = armJoints[2]
        
        return shoulderJoint, elbowJoint, wristJoint
        
    elif bodyPart == "leg":
            
        hipJoint = armJoints[0]
        kneeJoint = armJoints[1]
        ankleJoint = armJoints[2]
        
        return hipJoint, kneeJoint, ankleJoint
        
shoulderJoint, elbowJoint, wristJoint = find_and_rename_joints("arm", "Lf")
 

This first function is what handles finding and renaming the joints that we are working with. It has parameters that allow the user to enter what body part this is and what side of the body this is. The function returns the three joints in a list. This function can be expanded to include more body parts as seen with the leg if else statement. 

make_distDim()

The next function is make_distDim()

def make_distDim():
    # making distDim
    elbowDist = cmds.getAttr(elbowJoint + ".tx")
    wristDist = cmds.getAttr(wristJoint + ".tx")
    totalArmLength = elbowDist + wristDist
    
    null = cmds.group(em=1)
    
    wristPos = cmds.xform(wristJoint, q=1, t=1, ws=1)
    cmds.setAttr(null+".t", wristPos[0], wristPos[1], wristPos[2])
        
    distDim = cmds.distanceDimension(sp = [0,0,0], ep = [1,0,0])
    locatorTrans = cmds.listConnections(distDim)
    locatorShape = cmds.listConnections(distDim, sh=1)
        
    cmds.disconnectAttr(locatorShape[0] + ".worldPosition[0]", distDim + ".startPoint")
    cmds.disconnectAttr(locatorShape[1] + ".worldPosition[0]", distDim + ".endPoint")
        
    cmds.connectAttr(shoulderJoint+".t", distDim + ".startPoint")
    cmds.connectAttr(null+".t", distDim + ".endPoint")
        
    cmds.delete(locatorTrans)
        
    return elbowDist, wristDist, totalArmLength, null, distDim
    
make_distDim()

 This function makes the distance dimension tool and connects the start point to the shoulder and the end point to a null group that is point constrained to the wrist joint. This serves to get the distance value between the shoulder and wrist joint, this will be used to make a condition for the arm stretch as well as a ratio for how much to move the elbow and wrist joints to stretch the arm. 

This function also gets the default length of the elbow and wrist joints as well as their combined length. 

The function returns the distance tool, the length of the elbow and wrist and their total length as well as the null group as it will be used again for the IK handle controller. 

IKHandle_and_CNTL()

def IKHandle_and_CNTL():

    # Making an IK handle
    
    IKHandle = cmds.ikHandle(sj = shoulderJoint, ee = wristJoint, s=1)
    
    # Creating a controler for the IK Handle
    
    CNTL = cmds.circle(nr = (0,0,1), c = (0,0,0), r = 1.5)
    cmds.setAttr(CNTL[0] + ".rx", 90.0)
    
    # moving controler to where the wrist joint and IK handle are
    
    wristPos = cmds.xform(wristJoint, q=1, t=1, ws=1)
    cmds.setAttr(CNTL[0] + ".t", wristPos[0], wristPos[1], wristPos[2])
    
    # freezing transformations and deleting history
    
    cmds.makeIdentity(CNTL[0], apply=True)
    cmds.delete(CNTL[0], constructionHistory=True)
    
    # parent constraining the Ik handle to the controler
    
    cmds.parentConstraint(CNTL[0], IKHandle[0])
    
    # point constraning the null to the controler
    
    cmds.pointConstraint(CNTL[0], null)

IKHandle_and_CNTL()

This function makes the IK handle as well as a nurbs circle to act as the controller. 

This function uses the cmds.ikHandle function. Since this entire script is meant to work when the shoulder joint is selected we just needed to specify what the start point of the ik handle would be, I also specified the shoulder joint was the end effector just in case. 

Next the function creates a controller for the ik handle. 

make_connections()

def make_connections():
    # Setting up conditions
    
    elbowDist = cmds.getAttr(elbowJoint + ".tx")
    wristDist = cmds.getAttr(wristJoint + ".tx")
    totalArmLength = elbowDist + wristDist
        
    distance = cmds.getAttr(distDim + ".distance")
    
    # creating the condition node
    
    conditionNode = cmds.createNode("condition")
    cmds.setAttr(conditionNode + ".operation", 2)
    
    # connecting the distance to the first term of the condition node and the total arm length to the second term
    
    cmds.connectAttr(distDim + ".distance", conditionNode + ".firstTerm")
    cmds.setAttr(conditionNode + ".secondTerm", totalArmLength)
    
    # creating multiply and divide nodes
    
    divideNode = cmds.createNode("multiplyDivide")
    cmds.setAttr(divideNode + ".operation", 2)
    
    cmds.connectAttr(distDim + ".distance", divideNode + ".input1X")
    cmds.setAttr(divideNode + ".input2X", totalArmLength)
    
    multiplyNode = cmds.createNode("multiplyDivide")
    
    # if condition is true
    
    cmds.connectAttr(divideNode + ".outputX", multiplyNode + ".input1X")
    cmds.connectAttr(divideNode + ".outputX", multiplyNode + ".input1Y")
    
    # if condition is false use default values
    
    cmds.setAttr(multiplyNode + ".input2X", elbowDist)
    cmds.setAttr(multiplyNode + ".input2Y", wristDist)
    
    # if condition is true
    
    cmds.connectAttr(multiplyNode + ".outputX", conditionNode + ".colorIfTrueR")
    cmds.connectAttr(multiplyNode + ".outputY", conditionNode + ".colorIfTrueG")
    
    # if condition is false
    
    cmds.setAttr(conditionNode + ".colorIfFalseR", elbowDist)
    cmds.setAttr(conditionNode + ".colorIfFalseG", wristDist)
    
    cmds.connectAttr(conditionNode + ".outColorR", elbowJoint + ".translateX")
    cmds.connectAttr(conditionNode + ".outColorG", wristJoint + ".translateX")

make_connections()

This function makes the condition node and the multiply and divide node. The condition node is set to greater than, the distDim is set to the first term and the totalArmLength is set to the second term. This maes is so the condition is true when the distDim is greater than the totalArmLength. The Divide node is used to make a ratio of distDim / totalArmLength that will be multiply to the length of the elbow and wrist joints so they stretch proportionally. The multiply node is used to multiply the ratio. The elbow length is added to the x inputs of the multiply node, the wrists length is added to the y input of the multiply node, and the output of the divide node is connected to both the x and y input. The outputs are then connected to the condition nodes colorIfTrue inputs. X goes to R and Y goes to G. The original lengths are set to the colorIfFalse values to maintain the length of the joints if the condition is false.

Trouble Shooting

I got the script to be mostly functional. I ran into an issue where when the condition for the stretchy arm is met, the wrist joint jumps to where the elbow joint is. The elbow joint was behaving as wanted but the wrist joint was not. I figured out that I accientdly wrote these lines:

cmds.connectAttr(divideNode + ".outputX", multiplyNode + ".input1X")
cmds.connectAttr(divideNode + ".outputX", multiplyNode + ".input1Y")

as 

cmds.connectAttr(divideNode + ".outputX", multiplyNode + ".input1X")
cmds.connectAttr(divideNode + ".outputY", multiplyNode + ".input1Y") 

so I was not using the output from the divide node which gave me the ratio on the Y input of the multiple node. This is what was causing the issue. 


Comments

Popular posts from this blog

Scripting for Animation and VFX: Week3

Final: Auto three point lighting set up

Scripting For Animation and VFX: Week 2