def build( root, fingerDict, side='rt', cleanUp=1 ): ''' Builds an fk hand with extendable knuckles Args: root - node at the location of the wrist fingerDict - dictionary with keys for the name of each digit and values which are the root node for each digit: e.g. { 'thumb':'lf_thumb1_defJnt', 'index':'lf_index1_defJnt' } For each key in fingerDict a digit will be created with segments based on the number of child joints in the chain. Any joints with the same worldSpace location as their child will become 'extend' joints ''' # Validate arguments if type(root) != type('hello') and type(root) != type(u'hello'): return showDialog( 'Argument Error', 'You must supply a root of type string or unicode' ) if type(fingerDict) != type({}): return showDialog( 'Argument Error', "fingerDict must be supplied as a dictionary/nwith a key for the name of each digit and a root node as its value/n e.g.{ 'thumb':'lf_thumb1_defJnt', 'index':'lf_index1_defJnt' }") # Build and align root groups xformGrp = cmds.group(empty=1) xformGrp = cmds.rename(xformGrp, common.getName( node=xformGrp, side=side, rigPart='hand', function='xform', nodeType='grp')) common.align(node=xformGrp, target=root) rigGrp = cmds.duplicate(xformGrp) rigGrp = cmds.rename(rigGrp, common.getName( node=rigGrp, side=side, rigPart='hand', function='rig', nodeType='grp')) cmds.parent( rigGrp, xformGrp ) # Root joint cmds.select(None) rootJnt = cmds.joint() rootJnt = cmds.rename(rootJnt, common.getName( node=rootJnt, side=side, rigPart='hand', function='root', nodeType='jnt')) common.align(rootJnt, xformGrp) cmds.parent(rootJnt, rigGrp) cmds.setAttr('%s.jointOrient' % rootJnt, 0, 0, 0 ) # Build fingers for key in fingerDict.keys(): fing = buildFinger( side=side, name=key, rootJnt=fingerDict[key], rootGrp=xformGrp, cleanUp=cleanUp ) cmds.parent(fing['jnts'][0], rigGrp) # Cleanup if cleanUp: cmds.setAttr( '%s.visibility' % rigGrp, 0 ) common.attrCtrl(nodeList=[rigGrp], attrList=['visibility']) return {'systemGrp':xformGrp}
def attrCtrl(lock=True, keyable=False, channelBox=False, nodeList=[], attrList=[]): ''' Takes a list of nodes and sets locks/unlocks shows/hides attributes in attrList ''' if nodeList: for node in nodeList: if attrList: for attr in attrList: if cmds.attributeQuery(attr, node=node, exists=True): cmds.setAttr('%s.%s' % (node, attr), lock=lock, keyable=keyable, channelBox=channelBox) else: return showDialog( 'Argument Error', 'No nodes supplied for attribute control' ) else: return showDialog( 'Argument Error', 'No nodes supplied for attribute control' )
def align( node=None, target=None, translate=True, orient=True ): ''' sets the translation and / or orientation of node to match target ''' # Validate that the correct arguments have been supplied if not node or not target: # If node and target aren't explicitly supplied, check for a valid selection to use sel = cmds.ls(sl=1, type='transform') if len( sel ) == 2: node, target = sel[0], sel[1] else: return showDialog( 'Argument Error', 'Cannot determine nodes to align' ) targetMatrix = cmds.xform( target, q=True, ws=1, matrix=True ) nodeMatrix = cmds.xform( node, q=True, ws=1, matrix=True ) if translate and orient: cmds.xform ( node, ws=1, matrix=targetMatrix ) elif translate: # set row4 x y z to row4 of targetMatrix nodeMatrix[ 12:-1 ] = targetMatrix[ 12:-1 ] cmds.xform ( node, ws=1, matrix=nodeMatrix ) elif orient: # set row4 x y z to row4 of nodeMatrix targetMatrix[ 12:-1 ] = nodeMatrix[ 12:-1 ] cmds.xform ( node, ws=1, matrix=targetMatrix )
def exportControl(fileName=None, ctrl=None): """ Exports control to file """ workspace = os.path.join(os.path.dirname(__file__), "..", "assets") if fileName == None: fileName = str(cmds.ls(selection=True)[0]) ctrlFile = os.path.join(workspace, "%s_CTRL.ma" % fileName) if os.path.exists(ctrlFile): if ( showDialog( title="WARNING", message="Character %s control file already exists. \n Overwrite File?" % fileName, button=["yes", "no"], ) != "yes" ): return "User cancelled" if ctrl == None: ctrl = str(cmds.ls(selection=True)[0]) cmds.select(ctrl) cmds.file(ctrlFile, es=1, f=1, type="mayaAscii")
def writeJoints( char, jointList ): ''' function to save joint data out to a json file joints are stored in a dictionary called jointDict with one key - 'joints' the value of jointDict['joints'] is another dictionary with a key corresponding to the name of each joint the value of each joint key is also a dictionary containing all the data for that joint ''' joints = {} xformAttrs = ['tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz', 'jox', 'joy', 'joz'] # Populate data for j in jointList: jointData = {} for attr in xformAttrs: jointData.setdefault( attr, cmds.getAttr('%s.%s' % (j, attr))) joints.setdefault( j, jointData ) jointDict = {'joints':joints} charDir = common.getCharDir( char ) jointFile = os.path.join( charDir, '%s_joints.py' % char) # Check to see if file exists if os.path.isfile(jointFile): if showDialog( title='WARNING', message='Overwrite existing joint data?', button=['yes', 'no'] ) != 'yes': return 'User cancelled' # Make sure a destination folder exists destDir = os.path.dirname( jointFile ) if not os.path.exists( destDir ): if showDialog( title='WARNING', message='Character %s Does not exist.\n Would you like to create it now?' % char, button=['yes', 'no'] ) != 'yes': return 'User cancelled' else: os.makedirs( destDir ) # Write data to disk f = open(jointFile, 'w') f.write(json.dumps(jointDict)) f.close() showDialog( 'Success!', 'Joint data dumped to file:\n"%s_joints.py".' % jointFile )
def exportSkel( char, jointList ): ''' exports the charName_skel.ma file from the supplied jointlist. If file exists, asks whether you want to overwrite. ''' skelFile = os.path.join(common.getCharDir( char ), '%s_skel.ma' % char) if os.path.exists(skelFile): if showDialog( title='WARNING', message='Character %s skeleton file Exists.\n Overwrite File?' % char, button=['yes', 'no'] ) != 'yes': return 'User cancelled' cmds.select(jointList) cmds.file(skelFile, es=1, f=1, type='mayaAscii')
def importSkel( char ): ''' imports the charName_skel.ma file from the character's directory if one exists. If no file exists, asks whether you want to import the default skel. ''' skelFile = os.path.join(common.getCharDir( char ), '%s_skel.ma' % char) if not os.path.exists(skelFile): if showDialog( title='WARNING', message='Character %s skeleton file not found.\n Would you like to load default skeleton?' % char, button=['yes', 'no'] ) != 'yes': return 'User cancelled' skelFile = os.path.join(common.getCharDir( 'defaultChar' ), 'defaultChar_skel.ma') cmds.file(skelFile, i=1, dns=1)
def pointsAlongVector( start='', end='', divisions=2 ): ''' returns a list of points that lie on a line between start and end 'divisions' specifies the number of points to return. divisions = 2 (default) will return the start and end points with one intermediate point: [ p1(start), p2, p3(end) ] start and end can be supplied as lists, tuples or nodes. If they are not supplied, and 2 scene nodes are selected will attempt to use their world space positions as start and end creates a vector by subtracting end from start stores length of vector normalizes vector multiplies normalized vector by length / divisions ''' startPos, endPos = None, None if not start or not end and len( cmds.ls( sl=True ) ) == 2: startPos = cmds.xform( cmds.ls( sl=True )[0], translation=True, query=True, ws=True ) endPos = cmds.xform( cmds.ls( sl=True )[1], translation=True, query=True, ws=True ) else: if type( start ) == type( 'hello' ) or type( start ) == type( u'hello' ): startPos = cmds.xform( str(start), translation=True, query=True, ws=True ) else: startPos = start if type( end ) == type( 'hello' ) or type( end ) == type( u'hello' ): endPos = cmds.xform( str(end), translation=True, query=True, ws=True ) else: endPos = end if not startPos or not endPos: return showDialog( 'Argument Error', 'Cannot determine start and end points' ) startVec = om.MVector(startPos[0], startPos[1], startPos[2]) endVec = om.MVector(endPos[0], endPos[1], endPos[2]) newVec = endVec - startVec segLength = newVec.length() / divisions newVec.normalize() points = [] points.append(tuple(startPos)) for p in range( 1, divisions ): point = newVec * segLength * p + startVec points.append((point.x, point.y, point.z)) points.append(tuple(endPos)) return points
def readJoints( char ): ''' reads joint data for the specified character. First, attemps to locate charName_joints.py in the character's local directory. If this fails, loads in the data from the default character ''' jointFile = os.path.join(common.getCharDir( char ), '%s_joints.py' % char) if not os.path.exists(jointFile): if showDialog( title='WARNING', message='Character %s joint data not found.\n Would you like to load default joint data?' % char, button=['yes', 'no'] ) != 'yes': return 'User cancelled' jointFile = os.path.join(common.getCharDir( 'defaultChar' ), 'defaultChar_joints.py') f = open(jointFile, 'r') jointDict = json.loads(f.readline()) f.close() return jointDict
def insertGroup( node=None ): ''' creates an empty group aligned to the selected node and inserts it into the hierarchy; ''' if not node: node = cmds.ls(sl=1)[0] if node: parent = cmds.listRelatives(node, p=1) grp = cmds.group(empty=1, n='%s_grp' % node) align(node=grp, target=node) if parent: cmds.parent(grp, parent) cmds.parent(node, grp) return grp else: return showDialog( 'Argument Error', 'Cannot determine the node you wish to group' )
def importGeo(char): ''' imports the charName_geo.ma file from the character's directory if one exists. If no file exists, asks whether you want to import the default geo. Returns a list of new nodes added to the scene (rnn flag) ''' geoFile = os.path.join(common.getCharDir( char ), '%s_geo.ma' % char) if not os.path.exists(geoFile): if showDialog( title='WARNING', message='Character %s geometry file not found.\n Would you like to load default geometry?' % char, button=['yes', 'no'] ) != 'yes': return 'User cancelled' geoFile = os.path.join(common.getCharDir( 'defaultChar' ), 'defaultChar_geo.ma') importList = cmds.file(geoFile, i=1, dns=1, rnn=1) meshNodes = [ m for m in importList if m.split('_')[-1] == 'mesh' ] return meshNodes
def importControl(fileName): """ Import control from file """ workspace = os.path.join(os.path.dirname(__file__), "..", "assets") ctrlFile = os.path.join(workspace, "%s_CTRL.ma" % fileName) if not os.path.exists(ctrlFile): if ( showDialog( title="WARNING", message="Character %s control file not found.\n Please choose an existing control" % fileName, button=["ok"], ) == "ok" ): return "User cancelled" cmds.file(ctrlFile, i=True, dns=True)
def getSkinWeights(char="", meshes=[]): """ saves skinweights of supplied or selected geometries to a file the data is store in nested dictionaries in this structure {geometry{vertexId{influenceId:weight}...}...} """ # abort if no char is given if char == "": return cmds.warning("No character selected. Please supply a character name in function call.") # abort if there no mesh supplied or selected if meshes == []: meshes = cmds.ls(sl=1) print("No meshes supplied as param, attempting to use selected objects(%s)." % meshes) if meshes == []: return cmds.warning("No mesh to export weights from. Please select a mesh.") # check if output file already exists charDir = common.getCharDir(char) skinWeightsFile = os.path.join(charDir, "%s_skin.py" % char) if os.path.isfile(skinWeightsFile): if ( cmds.confirmDialog(title="Warning", message="Overwrite existing skin weights data?", button=["yes", "no"]) != "yes" ): return "User cancelled" allWeights = {} for mesh in meshes: # find skinCluster clusterName = mel.eval("findRelatedSkinCluster " + mesh) # abort if there is no skinCluster if clusterName == "": cmds.warning("No skinCluster found, " + mesh + " is not bound to a skeleton?") meshes.remove(mesh) break # prune to get rid of 0 zero value indices normState = cmds.getAttr("%s.normalizeWeights" % clusterName) shapeName = cmds.listRelatives(mesh, shapes=True)[0] cmds.setAttr("%s.normalizeWeights" % clusterName, False) cmds.skinPercent(clusterName, shapeName, normalize=False, pruneWeights=0.001) cmds.setAttr("%s.normalizeWeights" % clusterName, normState) # get the MFnSkinCluster for clusterName selList = om.MSelectionList() selList.add(clusterName) clusterNode = om.MObject() selList.getDependNode(0, clusterNode) skinFn = oma.MFnSkinCluster(clusterNode) # get the MDagPath for all influence infDags = om.MDagPathArray() skinFn.influenceObjects(infDags) # create a dictionary whose key is the MPlug indice id and # whose value is the influence list id infIds = {} infs = [] for i in xrange(infDags.length()): infPath = infDags[i].fullPathName() infId = int(skinFn.indexForInfluenceObject(infDags[i])) infIds[infId] = i infs.append(infPath) # get weightList and weight plugs weightListPlug = skinFn.findPlug("weightList") weightsPlug = skinFn.findPlug("weights") weightListAttr = weightListPlug.attribute() weightsAttr = weightsPlug.attribute() weightsInfIds = om.MIntArray() # the weights are stored in dictionary, the key is the vertId, # the value is another dictionary whose key is the influence id and # value is the weight for that influence # first key 'infs' is the list of influences in bind order weights = {} weights["infs"] = infs for vertId in xrange(weightListPlug.numElements()): vertWeights = {} weightsPlug.selectAncestorLogicalIndex(vertId, weightListAttr) weightsPlug.getExistingArrayAttributeIndices(weightsInfIds) infPlug = om.MPlug(weightsPlug) for infId in weightsInfIds: infPlug.selectAncestorLogicalIndex(infId, weightsAttr) try: vertWeights[infIds[infId]] = infPlug.asDouble() except KeyError: pass weights[vertId] = vertWeights allWeights[mesh] = weights # save data to file f = open(skinWeightsFile, "w") f.write(json.dumps(allWeights)) f.close() showDialog("Succss!", 'Skin weights for [ %s ] saved to file:\n"%s_skin.py"' % (", ".join(meshes), skinWeightsFile)) return allWeights
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
def setSkinWeights(char="", meshes=[]): """ applies skinweights from file to supplied or selected geometries (which need to already have a skincluster) """ # abort if no char is given if char == "": return cmds.warning("No character selected. Please supply a character name in function call.") # abort if there no mesh supplied or selected if meshes == []: meshes = cmds.ls(sl=1) print("No meshes supplied as param, attempting to use selected objects(%s)." % ", ".join(meshes)) if meshes == []: return cmds.warning("No mesh to export weights from. Please select a mesh.") # get path to skin file charDir = common.getCharDir(char) skinWeightsFile = os.path.join(charDir, "%s_skin.py" % char) # use default skinWeightsFile if character specific file does not exist if not os.path.exists(skinWeightsFile): skinWeightsFile = os.path.join(common.getCharDir("defaultChar"), "defaultChar_skin.py") cmds.warning("Did not find skin weights file for selected character, using default character file.") # read skin weights from file f = open(skinWeightsFile, "r") allWeights = json.loads(f.readline()) f.close() # compare meshes to keys of allWeights dictionary print("File contains skinweights for: " + ",".join(allWeights.keys())) for mesh in meshes: if mesh not in allWeights.keys(): cmds.warning("No weights found for %s" % mesh) meshes.remove(mesh) for mesh in meshes: # find skinCluster clusterName = mel.eval("findRelatedSkinCluster " + mesh) if clusterName == "": cmds.warning("No skinclutser found on %s." % mesh) break weights = allWeights[mesh] # unlock influences infs = cmds.skinCluster(clusterName, q=True, weightedInfluence=True) for inf in infs: cmds.setAttr("%s.liw" % inf, False) # prune weights to get rid of weights not stored in skinWeightsFile # normalize needs to be turned off for the prune to work normState = cmds.getAttr("%s.normalizeWeights" % clusterName) shapeName = cmds.listRelatives(mesh, shapes=True)[0] cmds.setAttr("%s.normalizeWeights" % clusterName, False) cmds.skinPercent(clusterName, shapeName, normalize=False, pruneWeights=100.0) cmds.setAttr("%s.normalizeWeights" % clusterName, normState) # pop influences key if "infs" in weights: weights.pop("infs") # set weights in skinCluster.weightsList[].weights[] to infValue for vertId, weightData in weights.items(): weightListAttr = "%s.weightList[%s]" % (clusterName, vertId) for infId, infValue in weightData.items(): weightsAttr = ".weights[%s]" % infId cmds.setAttr(weightListAttr + weightsAttr, infValue) showDialog( "Succss!", 'Skin weights applied to [ %s ] from file:\n"%s_skin.py"' % (", ".join(meshes), skinWeightsFile) ) return
def build( joint=None, twistAxis='x', name='' ): ''' Creates a duplicate of the specified joint which will remove the twist. Based on Victor Vinyal's nonroll setup Duplicates the joint and deletes all children apart from the first child joint ( there must at least a single child joint in the hierarchy ) Creates an RP IKHandle with the pole vector set to zero Parents the pole vector to the original joint point constrains the nonRoll to the joint groups the nonRoll outside the hierarchy Also creates an 'info' locator which is parented to 'nonRoll' and aim constrained to 'nonRollEnd' using 'joint' as its up vector - - this is the node from which to read the twist value ''' if not joint: if len(cmds.ls(sl=1, exactType='joint'))==1: joint = cmds.ls(sl=1, exactType='joint')[0] else: return showDialog( 'Argument Error', 'Please provide a joint' ) prefix = '%s_%s' % (common.getSide(joint), name) # main group for nonRoll system main_grp = cmds.group( empty=1, n='%s_nonRoll_grp' % prefix ) common.align( main_grp, joint ) # Duplicate the joints and delete all but first child joint copies = cmds.duplicate( joint, name='%s_nonRoll_jnt' % prefix, rc=1 ) nonRoll = copies[ 0 ] nonRollEnd = cmds.listRelatives( nonRoll, c=1, type='joint' )[ 0 ] deleteList = [ c for c in copies if not c in [ nonRoll, nonRollEnd ] ] if deleteList: cmds.delete( deleteList ) nonRollEnd = cmds.rename( nonRollEnd, '%s_nonRoll_end' % prefix ) rad = cmds.getAttr( '%s.radius' % nonRoll ) * 1.5 cmds.setAttr( '%s.radius' % nonRoll, rad ) cmds.setAttr( '%s.radius' % nonRollEnd, rad ) cmds.parent( nonRoll, main_grp ) cmds.pointConstraint( joint, nonRoll ) # build ikHandle ikHandle = cmds.ikHandle( sj=nonRoll, ee=nonRollEnd, n='%s_nonRoll_ikHandle' % prefix, sol='ikRPsolver' )[ 0 ] cmds.setAttr( '%s.poleVectorX' % ikHandle, 0 ) cmds.setAttr( '%s.poleVectorY' % ikHandle, 0 ) cmds.setAttr( '%s.poleVectorZ' % ikHandle, 0 ) cmds.parent( ikHandle, joint ) # build info locator info = cmds.spaceLocator( n='%s_nonRoll_info' % prefix )[ 0 ] common.align( info, joint ) cmds.parent( info, nonRoll ) if cmds.getAttr( '%s.t%s' % (nonRollEnd, twistAxis)) < 0: aimVec = ( -1, 0, 0 ) else: aimVec = ( 1, 0, 0 ) cmds.aimConstraint( nonRollEnd, info, aimVector=aimVec, wut='objectrotation', wuo=joint ) returnDict = { 'main_grp':main_grp, 'nonRoll':nonRoll, 'nonRollEnd':nonRollEnd, 'ikHandle':ikHandle, 'info':info } return returnDict
def rebindSkinClusters(char="", meshes=[]): """ delete and recreate skinclusters on supplied or selected meshes with influences from skinWeightsFile """ # abort if no char is given if char == "": return cmds.warning("No character selected. Please supply a character name in function call.") # abort if there no mesh supplied or selected if meshes == []: meshes = cmds.ls(sl=1) print("No meshes supplied as param, attempting to use selected objects(%s)." % ", ".join(meshes)) if meshes == []: return cmds.warning("No mesh to found. Please select a mesh.") # confirm rebind if ( cmds.confirmDialog( title="Warning", message="Delete and rebind skinclusters for [ %s ]?" % ", ".join(meshes), button=["yes", "no"], ) != "yes" ): return "User cancelled" # get path to skin file charDir = common.getCharDir(char) skinWeightsFile = os.path.join(charDir, "%s_skin.py" % char) # use default skinWeightsFile if character specific file does not exist if not os.path.exists(skinWeightsFile): skinWeightsFile = os.path.join(common.getCharDir("defaultChar"), "defaultChar_skin.py") cmds.warning("Did not find skin weights file for selected character, using default character file.") # read skin weights from file f = open(skinWeightsFile, "r") allWeights = json.loads(f.readline()) f.close() # remove meshes for which there is no infs-key or weight dictionary for mesh in meshes: if mesh not in allWeights.keys(): cmds.warning("No weights dictionary found for %s" % mesh) meshes.remove(mesh) if "infs" not in allWeights[mesh].keys(): cmds.warning("No Influences key found for %s" % mesh) meshes.remove(mesh) skinclusters = [] for mesh in meshes: weights = allWeights[mesh] # unbind mesh if skincluster exists clusterName = mel.eval("findRelatedSkinCluster " + mesh) if clusterName != "": cmds.skinCluster(mesh, edit=True, unbind=True) # rebind skincluster = cmds.skinCluster( weights["infs"], mesh, name="skincluster_" + str(mesh), toSelectedBones=True, maximumInfluences=5, obeyMaxInfluences=False, skinMethod=0, )[0] skinclusters.append(skincluster) showDialog( "Succss!", 'Skinclusters rebound on [ %s ] from file:\n"%s_skin.py"' % (", ".join(meshes), skinWeightsFile) ) cmds.select(meshes) return skinclusters