def setSkinWeights( skinCluster, vertJointWeightData ): ''' vertJointWeightData is a list of 2-tuples containing the vertex component name, and a list of 2-tuples containing the joint name and weight. ie it looks like this: [ ('someMesh.vtx[0]', [('joint1', 0.25), 'joint2', 0.75)]), ('someMesh.vtx[1]', [('joint1', 0.2), 'joint2', 0.7, 'joint3', 0.1)]), ... ] ''' #convert the vertex component names into vertex indices idxJointWeight = [] for vert, jointsAndWeights in vertJointWeightData: idx = int( vert[ vert.rindex( '[' )+1:-1 ] ) idxJointWeight.append( (idx, jointsAndWeights) ) #get an MObject for the skin cluster node skinCluster = apiExtensions.asMObject( skinCluster ) skinFn = MFnSkinCluster( skinCluster ) #construct a dict mapping joint names to joint indices jApiIndices = {} _tmp = MDagPathArray() skinFn.influenceObjects( _tmp ) for n in range( _tmp.length() ): jApiIndices[ str( _tmp[n].node() ) ] = skinFn.indexForInfluenceObject( _tmp[n] ) weightListP = skinFn.findPlug( "weightList" ) weightListObj = weightListP.attribute() weightsP = skinFn.findPlug( "weights" ) tmpIntArray = MIntArray() baseFmtStr = str( skinCluster ) +'.weightList[%d]' #pre build this string: fewer string ops == faster-ness! for vertIdx, jointsAndWeights in idxJointWeight: #we need to use the api to query the physical indices used weightsP.selectAncestorLogicalIndex( vertIdx, weightListObj ) weightsP.getExistingArrayAttributeIndices( tmpIntArray ) weightFmtStr = baseFmtStr % vertIdx +'.weights[%d]' #clear out any existing skin data - and awesomely we cannot do this with the api - so we need to use a weird ass mel command for n in range( tmpIntArray.length() ): removeMultiInstance( weightFmtStr % tmpIntArray[n] ) #at this point using the api or mel to set the data is a moot point... we have the strings already so just use mel for joint, weight in jointsAndWeights: if weight: try: infIdx = jApiIndices[ joint ] except KeyError: try: infIdx = jApiIndices[ joint.split( '|' )[0] ] except KeyError: continue setAttr( weightFmtStr % infIdx, weight )
def setSkinWeights(skinCluster, vertJointWeightData): ''' vertJointWeightData is a list of 2-tuples containing the vertex component name, and a list of 2-tuples containing the joint name and weight. ie it looks like this: [ ('someMesh.vtx[0]', [('joint1', 0.25), 'joint2', 0.75)]), ('someMesh.vtx[1]', [('joint1', 0.2), 'joint2', 0.7, 'joint3', 0.1)]), ... ] ''' #convert the vertex component names into vertex indices idxJointWeight = [] for vert, jointsAndWeights in vertJointWeightData: idx = int(vert[vert.rindex('[') + 1:-1]) idxJointWeight.append((idx, jointsAndWeights)) #get an MObject for the skin cluster node skinCluster = apiExtensions.asMObject(skinCluster) skinFn = MFnSkinCluster(skinCluster) #construct a dict mapping joint names to joint indices jApiIndices = {} _tmp = MDagPathArray() skinFn.influenceObjects(_tmp) for n in range(_tmp.length()): jApiIndices[str(_tmp[n].node())] = skinFn.indexForInfluenceObject( _tmp[n]) weightListP = skinFn.findPlug("weightList") weightListObj = weightListP.attribute() weightsP = skinFn.findPlug("weights") tmpIntArray = MIntArray() baseFmtStr = str( skinCluster ) + '.weightList[%d]' #pre build this string: fewer string ops == faster-ness! for vertIdx, jointsAndWeights in idxJointWeight: #we need to use the api to query the physical indices used weightsP.selectAncestorLogicalIndex(vertIdx, weightListObj) weightsP.getExistingArrayAttributeIndices(tmpIntArray) weightFmtStr = baseFmtStr % vertIdx + '.weights[%d]' #clear out any existing skin data - and awesomely we cannot do this with the api - so we need to use a weird ass mel command for n in range(tmpIntArray.length()): removeMultiInstance(weightFmtStr % tmpIntArray[n]) #at this point using the api or mel to set the data is a moot point... we have the strings already so just use mel for joint, weight in jointsAndWeights: if weight: try: infIdx = jApiIndices[joint] except KeyError: try: infIdx = jApiIndices[joint.split('|')[0]] except KeyError: continue setAttr(weightFmtStr % infIdx, weight)
def SaveTFXBoneBinaryFile(filepath, selected_mesh_shape_name, meshShapedagPath, rootPositions): #--------------------------------------------------------------------------- # Build a face/triangle index list to convert face index into triangle index #--------------------------------------------------------------------------- faceIter = OpenMaya.MItMeshPolygon(meshShapedagPath) triangleCount = 0 faceTriaIndexList = [] index = 0 util = OpenMaya.MScriptUtil() util.createFromInt(0) while not faceIter.isDone(): faceTriaIndexList.append(triangleCount) if faceIter.hasValidTriangulation(): numTrianglesPtr = util.asIntPtr() faceIter.numTriangles(numTrianglesPtr) numTriangles = util.getInt(numTrianglesPtr) triangleCount += numTriangles faceIter.next() #print "TressFX: Triangle count:%d\n" % triangleCount #---------------------- # Find the closest face #---------------------- meshFn = OpenMaya.MFnMesh(meshShapedagPath) meshIntersector = OpenMaya.MMeshIntersector() meshIntersector.create(meshShapedagPath.node()) triangleCounts = OpenMaya.MIntArray() triangleVertexIndices = OpenMaya.MIntArray( ) # the size of this array is three times of the number of total triangles meshFn.getTriangles(triangleCounts, triangleVertexIndices) vertexTriangleList = [] triangleIdForStrandsList = [] baryCoordList = [] uvCoordList = [] pointOnMeshList = [] progressBar = ProgressBar('Collecting bone data', len(rootPositions)) for i in range(len(rootPositions)): rootPoint = rootPositions[i] # Find the closest point info meshPt = OpenMaya.MPointOnMesh() meshIntersector.getClosestPoint(rootPoint, meshPt) pt = meshPt.getPoint() pointOnMesh = OpenMaya.MPoint() pointOnMesh.x = pt.x pointOnMesh.y = pt.y pointOnMesh.z = pt.z pointOnMeshList.append(pointOnMesh) # Find face index faceId = meshPt.faceIndex() # Find triangle index triangleId = faceTriaIndexList[faceId] + meshPt.triangleIndex() dummy = OpenMaya.MScriptUtil() dummyIntPtr = dummy.asIntPtr() triangleId_local = meshPt.triangleIndex( ) # This values is either 0 or 1. It is not a global triangle index. triangleId is the global triangle index. pointArray = OpenMaya.MPointArray() vertIdList = OpenMaya.MIntArray() faceIter.setIndex(faceId, dummyIntPtr) faceIter.getTriangle(triangleId_local, pointArray, vertIdList, OpenMaya.MSpace.kWorld) vertexTriangleList.append( (vertIdList[0], vertIdList[1], vertIdList[2])) triangleIdForStrandsList.append(triangleId) # Find three vertex indices for the triangle. Following two lines should give us three correct vertex indices for the triangle. I haven't really verified though. #vertexIndex = [triangleVertexIndices[triangleId*3], triangleVertexIndices[triangleId*3+1], triangleVertexIndices[triangleId*3+2]] vertexIndex = [vertIdList[0], vertIdList[1], vertIdList[2]] # Find barycentric coordinates uvw = OpenMaya.MPoint() # Somehow, below code gives me negative barycentric coordinates sometimes. # uPtr = OpenMaya.MScriptUtil().asFloatPtr() # vPtr = OpenMaya.MScriptUtil().asFloatPtr() # meshPt.getBarycentricCoords(uPtr,vPtr) # uvw.x = OpenMaya.MScriptUtil(uPtr).asFloat() # uvw.y = OpenMaya.MScriptUtil(vPtr).asFloat() # uvw.z = 1.0 - uvw.x - uvw.y # Instead getting barycentric coords from getBarycentricCoords, we compute it by the following function. uvw_a = ComputeBarycentricCoordinates(pointArray[0], pointArray[1], pointArray[2], pointOnMesh) uvw.x = uvw_a[0] uvw.y = uvw_a[1] uvw.z = uvw_a[2] # barycentric coordinates should be non-zero uvw.x = max(uvw.x, 0) uvw.y = max(uvw.y, 0) uvw.z = max(uvw.z, 0) # normalize barycentric coordinates so that their sum is equal to 1 sum = uvw.x + uvw.y + uvw.z uvw.x /= sum uvw.y /= sum uvw.z /= sum baryCoordList.append(uvw) # Find UV coordinates - We don't really use UV coords for tfxbone file. # util = OpenMaya.MScriptUtil() # util.createFromList([0.0, 0.0], 2) # uv_ptr = util.asFloat2Ptr() # meshFn.getUVAtPoint(rootPoint, uv_ptr) # u = OpenMaya.MScriptUtil.getFloat2ArrayItem(uv_ptr, 0, 0) # v = OpenMaya.MScriptUtil.getFloat2ArrayItem(uv_ptr, 0, 1) # uv_coord = OpenMaya.MPoint() # uv_coord.x = u # uv_coord.y = v # uv_coord.z = 0 # uvCoordList.append(uv_coord) # update progress gui progressBar.Increment() progressBar.Kill() #------------------------- # Get skin cluster object #------------------------- skinClusterName = '' skinClusters = cmds.listHistory(selected_mesh_shape_name) skinClusters = cmds.ls(skinClusters, type="skinCluster") if skinClusters: skinClusterName = skinClusters[0] else: cmds.warning('No skin cluster found on ' + selected_mesh_shape_name) return #print skinClusterName #--------------------------------------------------------------------------------------------------- # TODO: Try the following method. # skins = filter(lambda skin: mesh.getShape() in skin.getOutputGeometry(), ls(type='skinCluster')) # if len(skins) > 0 : # skin = skins[0] # skinJoints = skin.influenceObjects(); # root = skinJoints[0] # while root.getParent() : # root = root.getParent() # skin.getWeights(mesh.verts[index]) # select(root, hierarchy=True, replace=True) # joints = ls(selection=True, transforms=True, type='joint') #--------------------------------------------------------------------------------------------------- # get the MFnSkinCluster using skinClusterName selList = OpenMaya.MSelectionList() selList.add(skinClusterName) skinClusterNode = OpenMaya.MObject() selList.getDependNode(0, skinClusterNode) skinFn = OpenMayaAnim.MFnSkinCluster(skinClusterNode) dagPaths = MDagPathArray() skinFn.influenceObjects(dagPaths) # influence object is a joint influenceObjectsNames = [] # get joint names for i in range(dagPaths.length()): influenceName = dagPaths[i].partialPathName() influenceObjectsNames.append( influenceName) # Need to remove namespace? skinMeshes = cmds.skinCluster(skinClusterName, query=1, geometry=1) geoIter = OpenMaya.MItGeometry(meshShapedagPath) infCount = OpenMaya.MScriptUtil() infCountPtr = infCount.asUintPtr() numVertices = geoIter.count() weightArray = [ 0 ] * TRESSFX_MAX_INFLUENTIAL_BONE_COUNT * numVertices # joint weight array for all vertices. Each vertex will have TRESSFX_MAX_INFLUENTIAL_BONE_COUNT weights. # It is initialized with zero for empty weight in case there are less weights than TRESSFX_MAX_INFLUENTIAL_BONE_COUNT . jointIndexArray = [ -1 ] * TRESSFX_MAX_INFLUENTIAL_BONE_COUNT * numVertices # joint index array for all vertices. It is initialized with -1 for an empty element in case # there are less weights than TRESSFX_MAX_INFLUENTIAL_BONE_COUNT . # collect bone weights for all vertices in the mesh index = 0 while geoIter.isDone() == False: weights = OpenMaya.MDoubleArray() skinFn.getWeights(meshShapedagPath, geoIter.currentItem(), weights, infCountPtr) weightJointIndexPairs = [] for i in range(len(weights)): pair = WeightJointIndexPair() pair.weight = weights[i] pair.joint_index = i weightJointIndexPairs.append(pair) weightJointIndexPairs.sort() a = 0 for j in range( min(len(weightJointIndexPairs), TRESSFX_MAX_INFLUENTIAL_BONE_COUNT)): weightArray[index * TRESSFX_MAX_INFLUENTIAL_BONE_COUNT + a] = weightJointIndexPairs[j].weight jointIndexArray[index * TRESSFX_MAX_INFLUENTIAL_BONE_COUNT + a] = weightJointIndexPairs[j].joint_index a += 1 index += 1 geoIter.next() #------------------------ # Save the tfxbone file. #------------------------ progressBar = ProgressBar( 'Saving a tfxbone file', len(influenceObjectsNames) + len(triangleIdForStrandsList)) f = open(filepath, "wb") # Number of Bones f.write(ctypes.c_int(len(influenceObjectsNames))) # Write all bone (joint) names for i in range(len(influenceObjectsNames)): # Bone Joint Index f.write(ctypes.c_int(i)) # Size of the string, add 1 to leave room for the nullterminate. f.write(ctypes.c_int(len(influenceObjectsNames[i]) + 1)) # Print the characters of the string 1 by 1. for j in range(len(influenceObjectsNames[i])): f.write(ctypes.c_byte(ord(influenceObjectsNames[i][j]))) # Add a zero to null terminate the string. f.write(ctypes.c_byte(0)) progressBar.Increment() # Number of Strands f.write(ctypes.c_int(len(triangleIdForStrandsList))) for i in range(len(triangleIdForStrandsList)): triangleId = triangleIdForStrandsList[i] # three vertex indices from one triangle - Following two lines should work equally but I haven't verified it yet. #vertexIndices = [triangleVertexIndices[triangleId*3], triangleVertexIndices[triangleId*3+1], triangleVertexIndices[triangleId*3+2]] vertexIndices = vertexTriangleList[i] baryCoord = baryCoordList[i] weightJointIndexPairs = GetSortedWeightsFromTriangleVertices( TRESSFX_MAX_INFLUENTIAL_BONE_COUNT, vertexIndices, jointIndexArray, weightArray, baryCoord) # Index, the rest should be self explanatory. f.write(ctypes.c_int(i)) f.write(ctypes.c_int(weightJointIndexPairs[0].joint_index)) f.write(ctypes.c_float(weightJointIndexPairs[0].weight)) f.write(ctypes.c_int(weightJointIndexPairs[1].joint_index)) f.write(ctypes.c_float(weightJointIndexPairs[1].weight)) f.write(ctypes.c_int(weightJointIndexPairs[2].joint_index)) f.write(ctypes.c_float(weightJointIndexPairs[2].weight)) f.write(ctypes.c_int(weightJointIndexPairs[3].joint_index)) f.write(ctypes.c_float(weightJointIndexPairs[3].weight)) progressBar.Increment() f.close() progressBar.Kill() return
def ExportMesh(filepath, meshShapedagPath): meshFn = OpenMaya.MFnMesh(meshShapedagPath) meshIntersector = OpenMaya.MMeshIntersector() meshIntersector.create(meshShapedagPath.node()) faceIdList = [] baryCoordList = [] points = OpenMaya.MPointArray() meshFn.getPoints(points, OpenMaya.MSpace.kWorld) normals = OpenMaya.MFloatVectorArray() meshFn.getVertexNormals(False, normals, OpenMaya.MSpace.kWorld) triangleCounts = OpenMaya.MIntArray() triangleVertexIndices = OpenMaya.MIntArray( ) # the size of this array is three times of the number of total triangles meshFn.getTriangles(triangleCounts, triangleVertexIndices) #------------------------- # Get skin cluster object #------------------------- skinClusterName = '' mesh_shape_name = meshFn.name() skinClusters = cmds.listHistory(mesh_shape_name) skinClusters = cmds.ls(skinClusters, type="skinCluster") if skinClusters: skinClusterName = skinClusters[0] else: cmds.warning('No skin cluster found on ' + mesh_shape_name) return #print skinClusterName # get the MFnSkinCluster using skinClusterName selList = OpenMaya.MSelectionList() selList.add(skinClusterName) skinClusterNode = OpenMaya.MObject() selList.getDependNode(0, skinClusterNode) skinFn = OpenMayaAnim.MFnSkinCluster(skinClusterNode) dagPaths = MDagPathArray() skinFn.influenceObjects(dagPaths) # influence object is a joint influenceObjectsNames = [] # get joint names for i in range(dagPaths.length()): influenceName = dagPaths[i].partialPathName() influenceObjectsNames.append( influenceName) # Need to remove namespace? skinMeshes = cmds.skinCluster(skinClusterName, query=1, geometry=1) geoIter = OpenMaya.MItGeometry(meshShapedagPath) infCount = OpenMaya.MScriptUtil() infCountPtr = infCount.asUintPtr() numVertices = geoIter.count() weightArray = [ 0 ] * TRESSFX_MAX_INFLUENTIAL_BONE_COUNT * numVertices # joint weight array for all vertices. Each vertex will have TRESSFX_MAX_INFLUENTIAL_BONE_COUNT weights. # It is initialized with zero for empty weight in case there are less weights than TRESSFX_MAX_INFLUENTIAL_BONE_COUNT . jointIndexArray = [ -1 ] * TRESSFX_MAX_INFLUENTIAL_BONE_COUNT * numVertices # joint index array for all vertices. It is initialized with -1 for an empty element in case # there are less weights than TRESSFX_MAX_INFLUENTIAL_BONE_COUNT . # collect bone weights for all vertices in the mesh index = 0 progressBar = ProgressBar('Collect data', numVertices) while geoIter.isDone() == False: weights = OpenMaya.MDoubleArray() skinFn.getWeights(meshShapedagPath, geoIter.currentItem(), weights, infCountPtr) weightJointIndexPairs = [] for i in range(len(weights)): pair = WeightJointIndexPair() pair.weight = weights[i] pair.joint_index = i weightJointIndexPairs.append(pair) weightJointIndexPairs.sort() a = 0 for j in range( min(len(weightJointIndexPairs), TRESSFX_MAX_INFLUENTIAL_BONE_COUNT)): weightArray[index * TRESSFX_MAX_INFLUENTIAL_BONE_COUNT + a] = weightJointIndexPairs[j].weight jointIndexArray[index * TRESSFX_MAX_INFLUENTIAL_BONE_COUNT + a] = weightJointIndexPairs[j].joint_index a += 1 index += 1 progressBar.Increment() geoIter.next() progressBar.Kill() #---------------------------------------------------------- # We collected all necessary data. Now save them in file. #---------------------------------------------------------- totalProgress = points.length() + triangleVertexIndices.length() / 3 + len( influenceObjectsNames) progressBar = ProgressBar('Export collision mesh', totalProgress) f = open(filepath, "w") f.write("# TressFX collision mesh exported by TressFX Exporter in Maya\n") # Write all bone (joint) names f.write("numOfBones %g\n" % (len(influenceObjectsNames))) f.write("# bone index, bone name\n") for i in range(len(influenceObjectsNames)): f.write("%d %s\n" % (i, influenceObjectsNames[i])) progressBar.Increment() # write vertex positions and skinning data f.write("numOfVertices %g\n" % (points.length())) f.write( "# vertex index, vertex position x, y, z, normal x, y, z, joint index 0, joint index 1, joint index 2, joint index 3, weight 0, weight 1, weight 2, weight 3\n" ) for vertexIndex in xrange(points.length()): point = points[vertexIndex] normal = normals[vertexIndex] weightJointIndexPairs = GetSortedWeightsFromOneVertex( TRESSFX_MAX_INFLUENTIAL_BONE_COUNT, vertexIndex, jointIndexArray, weightArray) f.write( "%g %g %g %g %g %g %g %g %g %g %g %g %g %g %g\n" % (vertexIndex, point.x, point.y, point.z, normal.x, normal.y, normal.z, weightJointIndexPairs[0].joint_index, weightJointIndexPairs[1].joint_index, weightJointIndexPairs[2].joint_index, weightJointIndexPairs[3].joint_index, weightJointIndexPairs[0].weight, weightJointIndexPairs[1].weight, weightJointIndexPairs[2].weight, weightJointIndexPairs[3].weight)) progressBar.Increment() # write triangle face indices f.write("numOfTriangles %g\n" % (triangleVertexIndices.length() / 3)) f.write( "# triangle index, vertex index 0, vertex index 1, vertex index 2\n") for i in range(triangleVertexIndices.length() / 3): f.write("%g %d %d %d\n" % (i, triangleVertexIndices[i * 3 + 0], triangleVertexIndices[i * 3 + 1], triangleVertexIndices[i * 3 + 2])) progressBar.Increment() f.close() progressBar.Kill() return