def build( side=None, name=None, startPos=None, endPos=None, jointCount=None, worldUpVector=None, worldUpObject=None, twistReader=None, doNotInvertUp=False ): ''' Creates a simple motionPath spline rig >> Required Inputs: side (string) name (string) startPos (string : name of a transform) endPos (string : name of a transform) jointCount (int) worldUpVector (string : ['x', 'y', 'z']) worldUpObject (string : name of a transform) twistReader (string : name of a transform) doNotInvertUp (bool : internal usage, helper to orient limbs modules ) << Outputs: A Dictionary with: { 'twist_curve' : curve that carries everything, this is skinned by other modules 'motionPaths_group' : a group containing all the motionPath groups of this twistSection 'joints_group' : a group containing all the (skin) joints of this twistSection } ''' # Skipping popup, this will always be called from a script if not side or not name or not startPos or not endPos or not jointCount or not worldUpVector or not worldUpObject or not twistReader: print 'twistSection: Error! Must supply all arguments.' return axisDict = {'x':[1,0,0], 'y':[0,1,0], 'z':[0,0,1]} # get all points using the excellent function pointsAlongVector endPoint = cmds.xform( endPos, translation=True, query=True, ws=True ) allPoints = pointsAlongVector( start=startPos, end=endPoint, divisions=3 ) crv = cmds.curve( d=3, p=(allPoints[0], allPoints[1], allPoints[2], allPoints[3]), k=(0, 0, 0, 1, 1, 1), n=side+'_'+name+'_crv' ) # iterates over the jointCount to create the motionPath groups, mp nodes and joints uStep = 1.0/(jointCount-1) mpGrp = cmds.group( empty=True, n=side+'_'+name+'_mp_grp' ) jntGrp = cmds.group( empty=True, n=side+'_'+name+'_jnt_grp' ) for i in range( 1, jointCount+1 ): mpNode = cmds.createNode( 'motionPath', n=side+'_'+name+'_'+str(i)+'_mp' ) cmds.connectAttr( crv+'.worldSpace[0]', mpNode+'.geometryPath' ) mpiGrp = cmds.group( empty=True, n=side+'_'+name+'_'+str(i)+'_mp_grp' ) for ax in ['x', 'y', 'z']: cmds.connectAttr( mpNode+'.'+ax+'Coordinate', mpiGrp+'.t'+ax ) cmds.connectAttr( mpNode+'.r'+ax, mpiGrp+'.r'+ax ) cmds.setAttr( mpNode+'.uValue', uStep*(i-1) ) cmds.setAttr( mpNode+'.fractionMode', 1 ) cmds.setAttr( mpNode+'.follow', 1 ) cmds.setAttr( mpNode+'.worldUpType', 2 ) cmds.setAttr( mpNode+'.worldUpVector', *axisDict[worldUpVector] ) cmds.setAttr( mpNode+'.frontAxis', 0 ) # x if worldUpVector == 'x': cmds.setAttr( mpNode+'.upAxis', 0 ) elif worldUpVector == 'y': cmds.setAttr( mpNode+'.upAxis', 1 ) elif worldUpVector == 'z': cmds.setAttr( mpNode+'.upAxis', 2 ) if side == 'rt': cmds.setAttr( mpNode+'.inverseFront', 1 ) if not doNotInvertUp: cmds.setAttr( mpNode+'.inverseUp', 1 ) cmds.connectAttr( worldUpObject+'.worldMatrix[0]', mpNode+'.worldUpMatrix' ) # skip rotation for first joint if i != 1: ucNode = cmds.createNode( 'unitConversion', n=side+'_'+name+'_'+str(i)+'' ) # this guy handles the twist distribution cmds.setAttr( ucNode+'.conversionFactor', uStep*(i-1) ) cmds.connectAttr( twistReader+'.rx', ucNode+'.input' ) cmds.connectAttr( ucNode+'.output', mpNode+'.frontTwist' ) jnt = cmds.createNode( 'joint', n=side+'_'+name+'_'+str(i)+'_jnt' ) cmds.setAttr( jnt+'.displayLocalAxis', 1 ) cmds.refresh() # weird, but avoids cycles in Maya2013 at least cmds.parentConstraint( mpiGrp, jnt ) cmds.parent( jnt, jntGrp ) cmds.parent( mpiGrp, mpGrp ) # not sure about this, following Duncan's prototype lead # it tries to parent the skinJoints group to a group called 'skin_grp' in the scene, if it exists skinGrp = 'skin_grp' if cmds.objExists( skinGrp ): cmds.parent( jntGrp, skinGrp ) # returning as dictionary so it's friendlier/more readable wherever this is going to be used returnDic = { 'twist_curve':crv, 'motionPaths_group':mpGrp, 'joints_group':jntGrp } return returnDic
def build( hips=None, chest=None, head=None, numSpineJoints=6, numHeadJoints=6, twistAxis="x", bendAxis="y", cleanUp=True ): """ function to create the fk spine with head isolation... requires 3 objects to be selected or supplied - hips, chest, head hips: location of the first spine joint - this joint doesn't inherit any chest rotation chest: where the chest ctrl is placed. It's rotation is distributed from the second spine joint up to this point head: location of the last spine joint """ axisDict = {"x": [1, 0, 0], "y": [0, 1, 0], "z": [0, 0, 1]} # Validate that the correct arguments have been supplied if not hips or not chest or not head: # If hips, chest anf head aren't explicitly supplied, check for a valid selection to use sel = cmds.ls(sl=1, type="transform") if len(sel) == 3: hips, chest, head = sel[0], sel[1], sel[2] else: return showDialog("Argument Error", "Cannot determine hips, chest and head points") # Get positions of spine and head joints spineLocs = common.pointsAlongVector(start=hips, end=chest, divisions=numSpineJoints - 1) headLocs = common.pointsAlongVector(start=chest, end=head, divisions=numHeadJoints - 1) spineLocs.extend(headLocs[1:]) xformGrp = cmds.group(empty=1) xformGrp = cmds.rename( xformGrp, common.getName(node=xformGrp, side="cn", rigPart="spine", function="xform", nodeType="grp") ) common.align(node=xformGrp, target=hips) aimConst = cmds.aimConstraint( chest, xformGrp, aimVector=axisDict[twistAxis], upVector=axisDict[bendAxis], worldUpVector=[1, 0, 0] ) cmds.delete(aimConst) # Build control objects hipCtrl = controls.Control( side="cn", rigPart="spine", function="hips", nodeType="ctrl", size=1.25, color="green", aimAxis="twistAxis" ) hipCtrl.circleCtrl() hipGrp = common.insertGroup(node=hipCtrl.control) hipGrp = cmds.rename(hipGrp, hipCtrl.control.replace("ctrl", "grp")) common.align(node=hipGrp, target=xformGrp) cmds.parent(hipGrp, xformGrp) chestCtrl = controls.Control( side="cn", rigPart="spine", function="chest", nodeType="ctrl", size=1.25, color="green", aimAxis="twistAxis" ) chestCtrl.circleCtrl() chestGrp = common.insertGroup(node=chestCtrl.control) chestGrp = cmds.rename(chestGrp, chestCtrl.control.replace("ctrl", "grp")) common.align(node=chestGrp, orient=False, target=chest) common.align(node=chestGrp, translate=False, target=xformGrp) cmds.parent(chestGrp, hipCtrl.control) headCtrl = controls.Control( side="cn", rigPart="spine", function="head", nodeType="ctrl", size=1.25, color="green", aimAxis="twistAxis" ) headCtrl.crownCtrl() headGrp = common.insertGroup(node=headCtrl.control) headGrp = cmds.rename(headGrp, headCtrl.control.replace("ctrl", "grp")) common.align(node=headGrp, target=head, orient=False) common.align(node=headGrp, target=xformGrp, translate=False) cmds.parent(headGrp, xformGrp) # Add attr to head ctrl for isolating head rotation cmds.addAttr(headCtrl.control, longName="isolate_rotation", at="double", keyable=True, min=0, max=1) # Build driven groups spineGrps = [] for i in range(len(spineLocs)): g = cmds.group(empty=1) g = cmds.rename( g, common.getName(node=g, side="cn", rigPart="spine", function="driven%s" % str(i + 1), nodeType="grp") ) spineGrps.append(g) cmds.setAttr("%s.t" % g, spineLocs[i][0], spineLocs[i][1], spineLocs[i][2]) common.align(node=g, target=xformGrp, translate=False) if i > 0: cmds.parent(g, spineGrps[i - 1]) else: cmds.parent(g, xformGrp) cmds.parentConstraint(hipCtrl.control, g) # Build joints spineJoints = [] for i in range(len(spineLocs)): cmds.select(spineGrps[i]) j = cmds.joint() common.align(node=j, target=xformGrp, translate=False) cmds.setAttr("%s.jointOrient" % j) j = cmds.rename( j, common.getName(node=j, side="cn", rigPart="spine", function="driven%s" % str(i + 1), nodeType="jnt") ) spineJoints.append(j) cmds.setAttr("%s.displayLocalAxis" % j, 1) # set rotation orders leanAxis = [axis for axis in ["x", "y", "z"] if not axis in [twistAxis, bendAxis]][0] rotateOrder = "%s%s%s" % (twistAxis, leanAxis, bendAxis) rotateOrderDict = {"xyz": 0, "yzx": 1, "zxy": 2, "xzy": 3, "yxz": 4, "zyx": 5} for i in ( [hipGrp, hipCtrl.control, chestGrp, chestCtrl.control, headGrp, headCtrl.control] + spineGrps + spineJoints ): cmds.setAttr("%s.rotateOrder" % i, rotateOrderDict[rotateOrder]) # connect driven groups and joints below chest to chest control rotations spineBendUC = cmds.createNode("unitConversion") spineBendUC = cmds.rename( spineBendUC, common.getName(node=spineBendUC, side="cn", rigPart="spine", function="bend", nodeType="conv") ) cmds.connectAttr("%s.r%s" % (chestCtrl.control, bendAxis), "%s.input" % spineBendUC) cmds.setAttr("%s.conversionFactor" % spineBendUC, 1.0 / (numSpineJoints - 1)) spineLeanUC = cmds.createNode("unitConversion") spineLeanUC = cmds.rename( spineLeanUC, common.getName(node=spineLeanUC, side="cn", rigPart="spine", function="lean", nodeType="conv") ) cmds.connectAttr("%s.r%s" % (chestCtrl.control, leanAxis), "%s.input" % spineLeanUC) cmds.setAttr("%s.conversionFactor" % spineLeanUC, 1.0 / (numSpineJoints - 1)) for grp in spineGrps[1:numSpineJoints]: cmds.connectAttr("%s.output" % spineBendUC, "%s.r%s" % (grp, bendAxis)) cmds.connectAttr("%s.output" % spineLeanUC, "%s.r%s" % (grp, leanAxis)) for i in range(numSpineJoints - 1): spineTwistUC = cmds.createNode("unitConversion") spineTwistUC = cmds.rename( spineTwistUC, common.getName(node=spineTwistUC, side="cn", rigPart="spine", function="twist%s" % i, nodeType="conv"), ) cmds.connectAttr("%s.r%s" % (chestCtrl.control, twistAxis), "%s.input" % spineTwistUC) cmds.setAttr("%s.conversionFactor" % spineTwistUC, 1.0 / (numSpineJoints - 1) * (i + 1)) cmds.connectAttr("%s.output" % spineTwistUC, "%s.r%s" % (spineJoints[i + 1], twistAxis)) # connect driven groups above chest to reverse chest control rotations + hips control rotations hipRotUC = cmds.createNode("unitConversion") hipRotUC = cmds.rename( hipRotUC, common.getName(node=hipRotUC, side="cn", rigPart="spine", function="hipRotate", nodeType="conv") ) cmds.setAttr("%s.conversionFactor" % hipRotUC, -1) cmds.connectAttr("%s.r" % hipCtrl.control, "%s.input" % hipRotUC) hipRotMD = cmds.createNode("multiplyDivide") hipRotMD = cmds.rename( hipRotMD, common.getName(node=hipRotMD, side="cn", rigPart="spine", function="hipRotate", nodeType="multDiv") ) cmds.connectAttr("%s.output" % hipRotUC, "%s.input1" % hipRotMD) cmds.connectAttr("%s.isolate_rotation" % headCtrl.control, "%s.input2X" % hipRotMD) cmds.connectAttr("%s.isolate_rotation" % headCtrl.control, "%s.input2Y" % hipRotMD) cmds.connectAttr("%s.isolate_rotation" % headCtrl.control, "%s.input2Z" % hipRotMD) chestRotUC = cmds.createNode("unitConversion") chestRotUC = cmds.rename( chestRotUC, common.getName(node=chestRotUC, side="cn", rigPart="spine", function="chestRotate", nodeType="conv") ) cmds.setAttr("%s.conversionFactor" % chestRotUC, -1) cmds.connectAttr("%s.r" % chestCtrl.control, "%s.input" % chestRotUC) chestRotMD = cmds.createNode("multiplyDivide") chestRotMD = cmds.rename( chestRotMD, common.getName(node=chestRotMD, side="cn", rigPart="spine", function="chestRotate", nodeType="multDiv"), ) cmds.connectAttr("%s.output" % chestRotUC, "%s.input1" % chestRotMD) cmds.connectAttr("%s.isolate_rotation" % headCtrl.control, "%s.input2X" % chestRotMD) cmds.connectAttr("%s.isolate_rotation" % headCtrl.control, "%s.input2Y" % chestRotMD) cmds.connectAttr("%s.isolate_rotation" % headCtrl.control, "%s.input2Z" % chestRotMD) headRotUC = cmds.createNode("unitConversion") headRotUC = cmds.rename( headRotUC, common.getName(node=headRotUC, side="cn", rigPart="spine", function="headRotate", nodeType="conv") ) cmds.setAttr("%s.conversionFactor" % headRotUC, 1) cmds.connectAttr("%s.r" % headCtrl.control, "%s.input" % headRotUC) # negative hip and chest rotation are added to head rotation. The negative values are piped through a multiplier to blend the head isolation off and on spineRotNegPMA = cmds.createNode("plusMinusAverage") spineRotNegPMA = cmds.rename( spineRotNegPMA, common.getName(node=spineRotNegPMA, side="cn", rigPart="spine", function="rotateNeg", nodeType="pma"), ) cmds.connectAttr("%s.output" % hipRotMD, "%s.input3D[0]" % spineRotNegPMA) cmds.connectAttr("%s.output" % chestRotMD, "%s.input3D[1]" % spineRotNegPMA) cmds.connectAttr("%s.output" % headRotUC, "%s.input3D[2]" % spineRotNegPMA) spineBendNegUC = cmds.createNode("unitConversion") spineBendNegUC = cmds.rename( spineBendNegUC, common.getName(node=spineBendNegUC, side="cn", rigPart="spine", function="bendNeg", nodeType="conv"), ) cmds.connectAttr("%s.output3D%s" % (spineRotNegPMA, bendAxis), "%s.input" % spineBendNegUC) cmds.setAttr("%s.conversionFactor" % spineBendNegUC, 1.0 / (numHeadJoints - 2)) spineLeanNegUC = cmds.createNode("unitConversion") spineLeanNegUC = cmds.rename( spineLeanNegUC, common.getName(node=spineLeanNegUC, side="cn", rigPart="spine", function="leanNeg", nodeType="conv"), ) cmds.connectAttr("%s.output3D%s" % (spineRotNegPMA, leanAxis), "%s.input" % spineLeanNegUC) cmds.setAttr("%s.conversionFactor" % spineLeanNegUC, 1.0 / (numHeadJoints - 2)) for grp in spineGrps[numSpineJoints:-1]: cmds.connectAttr("%s.output" % spineBendNegUC, "%s.r%s" % (grp, bendAxis)) cmds.connectAttr("%s.output" % spineLeanNegUC, "%s.r%s" % (grp, leanAxis)) # Direct output from chest control. If head isolation is off. All twist joints above it recieve 100% of its twisting chestTwistDirectUC = cmds.createNode("unitConversion") chestTwistDirectUC = cmds.rename( chestTwistDirectUC, common.getName(node=chestTwistDirectUC, side="cn", rigPart="spine", function="chestTwist", nodeType="conv"), ) cmds.connectAttr("%s.r%s" % (chestCtrl.control, twistAxis), "%s.input" % chestTwistDirectUC) cmds.setAttr("%s.conversionFactor" % chestTwistDirectUC, 1.0) for i in range(numSpineJoints + 1, len(spineJoints)): # gradually negate hip twist from chest up hipTwistUC = cmds.createNode("unitConversion") hipTwistUC = cmds.rename( hipTwistUC, common.getName(node=hipTwistUC, side="cn", rigPart="spine", function="hipTwist%s" % i, nodeType="conv"), ) cmds.connectAttr("%s.r%s" % (hipCtrl.control, twistAxis), "%s.input" % hipTwistUC) cmds.setAttr("%s.conversionFactor" % hipTwistUC, -1.0 / (numHeadJoints - 2) * (i - numSpineJoints)) # gradually negate chest twist from chest up chestTwistUC = cmds.createNode("unitConversion") chestTwistUC = cmds.rename( chestTwistUC, common.getName(node=chestTwistUC, side="cn", rigPart="spine", function="chestTwist%s" % i, nodeType="conv"), ) cmds.connectAttr("%s.r%s" % (chestCtrl.control, twistAxis), "%s.input" % chestTwistUC) cmds.setAttr("%s.conversionFactor" % chestTwistUC, 1.0 / (numHeadJoints - 2) * (len(spineJoints) - (i + 1))) # sum hip and chest negation spineTwistNegPMA = cmds.createNode("plusMinusAverage") spineTwistNegPMA = cmds.rename( spineTwistNegPMA, common.getName( node=spineTwistNegPMA, side="cn", rigPart="spine", function="twistNeg%s" % i, nodeType="pma" ), ) cmds.connectAttr("%s.output" % hipTwistUC, "%s.input1D[0]" % spineTwistNegPMA) cmds.connectAttr("%s.output" % chestTwistUC, "%s.input1D[1]" % spineTwistNegPMA) # Distribute head twist down to chest spineTwistUC = cmds.createNode("unitConversion") spineTwistUC = cmds.rename( spineTwistUC, common.getName(node=spineTwistUC, side="cn", rigPart="spine", function="twist%s" % i, nodeType="conv"), ) cmds.connectAttr("%s.r%s" % (headCtrl.control, twistAxis), "%s.input" % spineTwistUC) cmds.setAttr("%s.conversionFactor" % spineTwistUC, 1.0 / (numHeadJoints - 2) * (i - numSpineJoints)) # Blend between hip and chest negation (head isolation on) and full inheritance of chest twist (head isolation off) spineTwistNegBC = cmds.createNode("blendColors") spineTwistNegBC = cmds.rename( spineTwistNegBC, common.getName(node=spineTwistNegBC, side="cn", rigPart="spine", function="twistNeg%s" % i, nodeType="bc"), ) cmds.connectAttr("%s.output1D" % spineTwistNegPMA, "%s.color1R" % spineTwistNegBC) cmds.connectAttr("%s.output" % chestTwistDirectUC, "%s.color2R" % spineTwistNegBC) cmds.connectAttr("%s.isolate_rotation" % headCtrl.control, "%s.blender" % spineTwistNegBC) # Sum blended negation twist and head twist spineTwistPMA = cmds.createNode("plusMinusAverage") spineTwistPMA = cmds.rename( spineTwistPMA, common.getName( node=spineTwistPMA, side="cn", rigPart="spine", function="twistResult%s" % i, nodeType="pma" ), ) cmds.connectAttr("%s.outputR" % spineTwistNegBC, "%s.input1D[0]" % spineTwistPMA) cmds.connectAttr("%s.output" % spineTwistUC, "%s.input1D[1]" % spineTwistPMA) # Force unit conversion to 1 spineTwistResultUC = cmds.createNode("unitConversion") spineTwistResultUC = cmds.rename( spineTwistResultUC, common.getName( node=spineTwistResultUC, side="cn", rigPart="spine", function="twistResult%s" % i, nodeType="conv" ), ) cmds.connectAttr("%s.output1D" % spineTwistPMA, "%s.input" % spineTwistResultUC) cmds.setAttr("%s.conversionFactor" % spineTwistResultUC, 1.0) cmds.connectAttr("%s.output" % spineTwistResultUC, "%s.r%s" % (spineJoints[i - 1], twistAxis)) # Orient contstrain last head joint to second last head joint orientConst = cmds.orientConstraint(spineJoints[-2], spineJoints[-1]) # Point constrain chest ctrl grp to its spine group pointConst = cmds.pointConstraint(spineGrps[numSpineJoints - 1], chestGrp) # Point constrain head ctrl grp to last spine group pointConst = cmds.pointConstraint(spineGrps[-1], headGrp) # Add orient constraint to headCtrl group - weighted between root noXformGrp and last spine grp - weight controlled by isolation attr orientConst = cmds.orientConstraint(xformGrp, chestCtrl.control, headGrp)[0] isolateRev = cmds.createNode("reverse") isolateRev = cmds.rename( isolateRev, common.getName(node=isolateRev, side="cn", rigPart="spine", function="headIsolate", nodeType="rev") ) cmds.connectAttr("%s.isolate_rotation" % headCtrl.control, "%s.inputX" % isolateRev) cmds.connectAttr("%s.isolate_rotation" % headCtrl.control, "%s.%sW0" % (orientConst, xformGrp), f=1) cmds.connectAttr("%s.outputX" % isolateRev, "%s.%sW1" % (orientConst, chestCtrl.control), f=1) cmds.setAttr("%s.interpType" % orientConst, 2) # Clean up attributes and objects that need to be hidden / locked if cleanUp: cmds.setAttr("%s.visibility" % spineGrps[0], 0) common.attrCtrl(nodeList=[spineGrps[1]], attrList=["visibility"]) common.attrCtrl(nodeList=[hipCtrl.control], attrList=["sx", "sy", "sz", "visibility"]) common.attrCtrl( nodeList=[chestCtrl.control, headCtrl.control], attrList=["tx", "ty", "tz", "sx", "sy", "sz", "visibility"] ) returnDict = collections.defaultdict(list) returnDict["xformGrp"].append(xformGrp) returnDict["hipCtrl"].append(hipCtrl.control) returnDict["chestCtrl"].append(chestCtrl.control) returnDict["headCtrl"].append(headCtrl.control) return returnDict