Hey guys,
As the title points out, I’m in the process of building a tool that creates a dynamic joint chain. Right now the tool works fine more or less but things really break down once you create a second chain.
The biggest problem at the moment is the following:
When I try and create a second chain (or even the first one) my ikHandle fails to connect to the Output Curve shape node. I did try and think of some sort of workaround in the code (line 153) to fix the issue…
ex. mc.connectAttr(outCurve[0] + “.worldSpace”, dynIKSol[0] + “.inCurve”)
…or at least warn the user what is happening (lines 164,165). I know there has to be a better approach.
Hopefully this is enough info to get you guys going in the right direction. I have provided comments throughout the code to help keep things simple. If anything is unclear, please let me know. I appreciate any help or suggestions.
-B
# nChain
# This script creates a dynamic joint chain that can be used for ojects such as rope,
# hair strands, or any 'hanging' object that interacts with gravity.
# Import Maya commands
import maya.cmds as mc
# Import Maya's MEL intepreter
import maya.mel as mel
# If window already exists, it will be replaced by new window
nChainUIName = "nChain_UI"
if mc.window("nChain_UI", exists=True):
mc.deleteUI("nChain_UI", window=True)
# nChain Window
def nChain_UI():
nChainWin = mc.window(nChainUIName, title="nChain UI", sizeable=True)
mc.columnLayout(adj=True)
mc.text(label="This tool creates a dynamic joint chain at world origin. Press PLAY to view results.", wordWrap=True, al="center")
mc.separator(height=10, style="in")
mc.text(label="Joint Creation Properties", wordWrap=True, al="center")
mc.separator(height=10, style="in")
mc.text(label="Please select Point Lock method, then specify nChain values.", wordWrap=True, al="center")
mc.radioButtonGrp("point_lock", numberOfRadioButtons=3, label="Lock Chain to Point(s): ", labelArray3=["Base", "Tip", "Both Ends"], select=1)
mc.intSliderGrp("number_of_joints", field=True, label="Number of Joints", min=2, max=25, fieldMaxValue=100, value=1)
mc.intSliderGrp("units_between_joints", field=True, label="Units Between Joints", min=1, max=10, fieldMaxValue=1000, value=1)
mc.floatSliderGrp("joint_radius", field=True, label="Joint Radius", fieldMinValue=0.1, min=1, max=10, fieldMaxValue=1000, value=1)
mc.separator(height=10, style="in")
mc.text(label="Wind Properties for Nucleus", wordWrap=True, al="center")
mc.separator(height=10, style="in")
mc.text(label="NOTE: The attributes below can be readjusted within the Nucleus Node under the 'Gravity and Wind' Tab.", wordWrap=True, al="center")
mc.floatSliderGrp("air_density", field=True, label="Air Density", min=0, max=10, fieldMaxValue=100000, value=1)
mc.floatSliderGrp("wind_speed", field=True, label="Wind Speed", min=0, max=50, fieldMaxValue=100000, value=0)
mc.floatFieldGrp("wind_direction", label="Wind Direction", numberOfFields=3, value1=1, value2=0, value3=0 )
mc.floatSliderGrp("wind_noise", field=True, label="Wind Noise", min=0, max=1, fieldMaxValue=100000, value=0)
mc.separator(height=25, style="in")
mc.button(label="Create nChain", width=10, height= 40, command="nChain.createNChain()")
mc.showWindow(nChainWin)
# Note: This tool will create a Hair System (including Nucleus Node), an ikHandle, and an Air Field
# nChain Creation
def createNChain():
# Create base joint and determine input values
mc.select(clear=True)
baseJoint = mc.joint(p=(0, 0, 0), name="base_nJoint")
# Number of joints and space between each joint
jntNumber = mc.intSliderGrp("number_of_joints", query=True, value=True)
spaceJoints = mc.intSliderGrp("units_between_joints", query=True, value=True)
for jNum in range(jntNumber-1):
chainJoint = mc.joint(p=(0, spaceJoints *-1, 0), name="nJoint_#", relative=True)
# Radius for each joint
jntRadius = mc.floatSliderGrp("joint_radius", query=True, value=True)
mc.select(baseJoint, replace=True) # selecting base joint only...
radiusSel= mc.setAttr(".radius", jntRadius) # setting radius.
for everyJoint in range(jNum+1): # selecting the rest of the joints in the chain...
mc.pickWalk(direction="down")
mc.setAttr(".radius", jntRadius) # setting radius value.
# Select base joint (top of hierarcy) and group
mc.select(baseJoint, replace=True)
masterGroup = mc.group(name="nJoint_grp_#")
pivTop = mc.xform(query=True, boundingBox=True) # moving pivot for group to base joint...
mc.xform(piv=(0, pivTop[4], 0), absolute=True) #...moved.
mc.select(clear=True) # at this point, joint creation is done.
# Create curve...
dynCurve = mc.curve(degree=3, point=(0, 0, 0))
mc.curve(dynCurve, append=True, degree=3, point=(0, spaceJoints *-1, 0))
for everyJoint in range(jNum): # point will be created for every joint in chain.
lastPoint = mc.xform(query=True, boundingBox=True)
mc.curve(dynCurve, append=True, degree=3, point=(0, lastPoint[1] + spaceJoints *-1, 0))
# Parent Curve to 'masterGroup'
mc.select(masterGroup, add=True)
mc.parent()
# Make Curve Dynamic using MEL Interpreter and place new objects in group
mel.eval('makeCurvesDynamic 2 { "0", "0", "1", "1", "0"}')
# Let's group a few extra nodes that were just created and put them in 'masterGroup'
hairSys = mc.ls(sl=True)
mc.select(masterGroup, add=True)
mc.parent() # hair system parented to group...
mc.select(clear=True)
checkConnect = mc.listConnections(hairSys, connections=True) # listing connections to access Nucleus node later on...
# Now we will group the last object parented to world and toss that into 'masterGroup'
mc.select(hairSys[0], replace=True)
newSel = mc.pickWalk(direction="up")
mc.select(newSel[0] + "OutputCurves", masterGroup)
mc.parent() # output curves group parented to 'masterGroup'...
mc.select(clear=True)
# Before moving on, we will determine our Point Lock method
mc.select(hairSys, replace=True)
mc.pickWalk(direction="up")
mc.pickWalk(direction="left")
mc.pickWalk(direction="down") # Follicle shape selection
shapeFollicle = mc.ls(sl=True)
# Point Lock method for radioButton)
pointLockOp = mc.radioButtonGrp("point_lock", query=True, select=True)
if pointLockOp == 1:
mc.setAttr(shapeFollicle[0] + ".pointLock", 1)
elif pointLockOp == 2:
mc.setAttr(shapeFollicle[0] + ".pointLock", 2)
elif pointLockOp == 3:
mc.setAttr(shapeFollicle[0] + ".pointLock", 3)
else:
print ""
# Time to add the IK Handle to dynamic drive the joint chain
# We have to select the Rotate Pivot's for base and end joints, otherwise the IK Handle won't function properly
mc.select("base_nJoint.rotatePivot", replace=True)
chainJointRP = mc.select (chainJoint + ".rotatePivot", add=True)
dynIKSol = mc.ikHandle(sol="ikSplineSolver", createCurve=False, twistType="easeInOut", parentCurve=False, rootTwistMode=True)
mc.select(dynCurve, add=True)
mc.select(clear=True)
# Input values for Wind Properties... starting with Air Density.
airDen = mc.floatSliderGrp("air_density", query=True, value=True)
mc.select(checkConnect[3], replace=True)
nucleusNode = mc.ls(sl=True)
mc.setAttr(nucleusNode[0] + ".airDensity", airDen)
# Now Wind Speed...
windSpeed = mc.floatSliderGrp("wind_speed", query=True, value=True)
mc.setAttr(nucleusNode[0] + ".windSpeed", windSpeed)
# Wind Direction...
windDir = mc.floatFieldGrp("wind_direction", query=True, value1=True)
mc.setAttr(nucleusNode[0] + ".windDirectionX", windDir) # input value for X
windDir = mc.floatFieldGrp("wind_direction", query=True, value2=True)
mc.setAttr(nucleusNode[0] + ".windDirectionY", windDir) # input value for Y
windDir = mc.floatFieldGrp("wind_direction", query=True, value3=True)
mc.setAttr(nucleusNode[0] + ".windDirectionZ", windDir) # input value for Z
# Lastly, Wind Noise
windNoise = mc.floatSliderGrp("wind_noise", query=True, value=True)
mc.setAttr(nucleusNode[0] + ".windNoise", windNoise)
# Repatch disconnected node!!
mc.select(clear=True)
mc.select(baseJoint, replace=True) # selcting 'base_nJoint'
mc.pickWalk(direction="right")
mc.pickWalk(direction="right")
mc.pickWalk(direction="right")
mc.pickWalk(direction="down") # output curve selected
outCurve = mc.pickWalk(direction="down") # output curve shape selected
# If you are creating more than one nChain:
# Make sure your output curve SHAPE node is connected to the ikHandle ".inCurve".
# Without this connection, nothing works.
mc.connectAttr(outCurve[0] + ".worldSpace", dynIKSol[0] + ".inCurve")
# To fininsh up...
mc.select(baseJoint, replace=True) # selcting 'base_nJoint'
mc.pickWalk(direction="right")
mc.pickWalk(direction="right")
mc.pickWalk(direction="right")
mc.pickWalk(direction="down") # output curve selected
shutVisibility = mc.ls(sl=True)
mc.setAttr(shutVisibility[0] + ".visibility", 0) # shutting off visibility for output curve
mc.select(clear=True)
# For more than one nChain, we will make sure connections are made here.
if mc.objExists("nJoint_grp_2"):
mc.error("If you are creating more than one nChain: Make sure your new output curve SHAPE node is connected to the ikHandle '.inCurve'. Without this connection, nothing works.")
else:
print "nChain created. Press Play to see results."
# End Script. Created by Bryan Godoy.