The Complete Research and Study on Maya Weight in Mel Python and C++ API

date: 2016.08.25 - 2016.09.25
by: ying

  • since the first time I paint weight in Maya 8.5 in a freelance rig project, re-paint the same weight is pain process
  • since 2012, starting with Maya MEL command “skinPercent” to start on customized weight operation
  • then till 2014, study more than 7 ways on faster weight saving and importing operation and usable completion of Python version of VMP tool
  • now 2016, the final completion of this long term project on researching for Fastest Way on Maya Weight operation.
  • Document the key concept and structure of Maya skin weight
    1. skincluster node and its data structure
    2. component editor and the weight table by joint and vertex
    3. logical index and physical index of joints in the SkinCluster node
    4. MObject, MDagPath, MFnSkinCluster and MDoubleArray - the 1 dimension weight list for vertex by joint
      • concepts of object oriented programming
      • concepts of passing by value, pointer, reference in C++ and Python
  • List characteristics of all the Maya weight related operation commands available in MEL, Python and C++ API
    1. skinPercent mel/python command
      • average weight, shift weight between joint on a vertex
    2. weightList attribute and weightPlug, setDouble API method
      • per weight table cell API weight operation
    3. getWeight and setWeight API method
      • large weight data getting and setting
  • wip

Logical Index and Physical Index of Joint in skincluster

Concept of Dynamic array - a pre-defined-size list with pre-loaded empty items

  • example MEL code
    string $nameArray[10];
    $nameArray[5]="objA"; // at place 5 in nameArray
    print("\n-------start sparse array--------\n");
    print $nameArray; // null null null null null objA
    print("-------end----------");
     
    $logical_index = stringArrayFind( "objA", 0, $nameArray ); // result: 5
    print("\nlogical index of 'objA': "+ $logical_index);
    print("\nsize: "+size($nameArray)); // size of array currently: 6
    print("\n9th: "+$nameArray[8]); // null
    print("\n11th: "+$nameArray[10]); // null
    print("\nsize: "+size($nameArray)); // size of array currently: 6
     
    string $nameArray_autoNonSparse_means_noEmpty[];
    for($each in $nameArray){
        if($each != ""){
            $cur_index = size($nameArray_autoNonSparse_means_noEmpty);
            $nameArray_autoNonSparse_means_noEmpty[$cur_index] = $each;
        }
    }
    print("\n-------start no sparse array--------\n");
    print($nameArray_autoNonSparse_means_noEmpty);
    print("-------end----------");
    $physical_index = stringArrayFind( "objA", 0, $nameArray_autoNonSparse_means_noEmpty ); // result: 0
    print("\nphysical index of 'objA': "+$physical_index+"\n");
     
    // clear from memory
    clear($nameArray);
    • so the logical index of a array is the number is in the [], and as like Python list and other language arrays
      • as shown in Maya skincluster joint list connection

The Physical index of joint in the influence list

  • the most obvious demonstration of physical index of joint is in the component editor - skin weight tab
    • since “joint3” is deleted, and no longer has relationship in “skinCluster1”, thus there is no point put “joint3” column in the weight table;
    • the weight table is actual representation of the weight data;
      • including all the vertex
      • including all the joints linked to skinCluster, even it has 0 in weight
        • (make sure in component editor > option menu > Uncheck hide zero column)
        • (make sure in component editor > option menu > Uncheck sort alphabetically column)
    • and when saving, actual weight data store in a 1 dimension array { vtx[0][jnt1], vtx[0][jnt2] … vtx[7][jnt2] }
      • with joint name as column header, and vtx index as row header
      • so column index is the actual Physical index of joint used in referring weight data table
      • while the logical index of joint used in referring current connection flow.

Get Physical Bone List, Bone Physical index, Logical index

  • code example in MEL/Python and C++ API way for Get Bone List, Bone Physical index, Logical index
    [u'joint1', u'joint2'] // physical bone index list
    [u'joint1', u'joint2']
    {u'joint2': 2, u'joint1': 0} // logical bone index list
    {u'joint2': 2, u'joint1': 0}

Physical list and index for bones

import maya.cmds as cmds
import maya.OpenMaya as OpenMaya
import maya.OpenMayaAnim as OpenMayaAnim
def getBoneList_OpenMaya(skinName):
    skinClusterSel = OpenMaya.MSelectionList()
    skinClusterObj = OpenMaya.MObject()
    OpenMaya.MGlobal.getSelectionListByName(skinName,skinClusterSel)
    skinClusterSel.getDependNode(0,skinClusterObj)
    skinClusterFn = OpenMayaAnim.MFnSkinCluster(skinClusterObj)
    # Get influence path list
    infPathArray = OpenMaya.MDagPathArray()
    skinClusterFn.influenceObjects(infPathArray)
    infNameArray = [infPathArray[i].partialPathName() for i in range(infPathArray.length())]
    print infNameArray
    return infNameArray
def getBoneList_Cmds(skinName, used_only=0):
    boneList = []
    if used_only == 0:
        boneList = cmds.skinCluster(skinName, q=1, inf=1) # all connected bones, includes 0 weight bone
    else:
        boneList = cmds.skinCluster(skinName, q=1, wi=1) # all connected bones, but exlude 0 weight bone
    print boneList
    return boneList
physical_bone_array = getBoneList_OpenMaya('skinCluster1')
physical_bone_array = getBoneList_Cmds('skinCluster1')

logical index for bones

def getBone_Logical_Index_Dict_Cmds(skinName):
    boneConnectionList = cmds.listConnections(skinName+'.matrix',c=1)
    connectionCount = len(boneConnectionList)/2
    boneDict = {}
    for i in xrange(connectionCount):
        boneDict[ boneConnectionList[i*2+1] ] = int( boneConnectionList[i*2].split('[')[1][:-1] )
    print boneDict
    return boneDict
 
def getBone_Logical_Index_Dict_OpenMaya(skinName):
    # Get skin object
    skinClusterSel = OpenMaya.MSelectionList()
    skinClusterObj = OpenMaya.MObject()
    OpenMaya.MGlobal.getSelectionListByName(skinName,skinClusterSel)
    skinClusterSel.getDependNode(0,skinClusterObj)
    skinClusterFn = OpenMayaAnim.MFnSkinCluster(skinClusterObj)
    # Get influence object
    infPathArray = OpenMaya.MDagPathArray()
    skinClusterFn.influenceObjects(infPathArray)
    infNameArray = [infPathArray[i].partialPathName() for i in range(infPathArray.length())]
    infLogicalIndexArray = [ skinClusterFn.indexForInfluenceObject(infPathArray[i]) for i in range(infPathArray.length())]
    boneDict = dict(zip(infNameArray, infLogicalIndexArray))
    print boneDict
    return boneDict 
 
bone_logical_dict = getBone_Logical_Index_Dict_Cmds('skinCluster1')
bone_logical_dict = getBone_Logical_Index_Dict_OpenMaya('skinCluster1')

Get Weight and Set Weight

There are 3 methods in get and set weight in Maya

  • skinPercent (mel,python)
  • plug API (python, c++)
  • getWeight, setWeight API (python, c++)

for the small object test, 200 vtx * 4 bones = 800 data

skinPercent VMP plug weightMap g/setWeight seconds
0.22 0.0099 0.020 store
0.28 0.0500 0.020 apply

method 01 - skinpercent command

  • wip

method 02 - attribute plug API

  • wip

method 03 - getWeight setWeight API

example to store weight like in component editor table (in weightList format)

wl = getWeight_cust_OpenMaya("body_skinCluster", ["Elbow_R","Wrist_R"], cmds.ls(sl=1,fl=1)) 
# with current selection of vtx list for rows, that 2 bones for column
weightAPI_code.py
# -- get weight by selected joint * selected vertex
def getWeight_cust_OpenMaya(skinName,influenceList,componentList):
    # weight table: 
    # ^    ^ jntB ^ jntC ^  ~ selected list of joints
    # |vtx4| b4   |  c4  |
    # |vtx5| b5   |  c5  |
    # ~ selected list of vtx
    # weight result: b4 c4 b5 c5
    startTime = cmds.timerX()
    # Get skinCluster Fn
    skinClusterSel = OpenMaya.MSelectionList()
    skinClusterObj = OpenMaya.MObject()
    OpenMaya.MGlobal.getSelectionListByName(skinName,skinClusterSel)
    skinClusterSel.getDependNode(0,skinClusterObj)
    skinFn = OpenMayaAnim.MFnSkinCluster(skinClusterObj)
    # Build selection list
    selectionList = OpenMaya.MSelectionList()
    selectionPath = OpenMaya.MDagPath()
    selectionObj = OpenMaya.MObject()
    if type(componentList) == str or type(componentList) == unicode: componentList = [str(componentList)]
    [selectionList.add(i) for i in componentList]
    selectionList.getDagPath(0,selectionPath,selectionObj)
    # Get influence List
    if type(influenceList) == str or type(influenceList) == unicode: influenceList = [str(influenceList)]
    influence_fullList = getBoneList_OpenMaya(skinName) # influence list and physical index
    infIndexArray = OpenMaya.MIntArray()
    [infIndexArray.append(influence_fullList.index(influence)) for influence in influenceList]
    # Get weight values
    weightList = OpenMaya.MDoubleArray()
    skinFn.getWeights(selectionPath,selectionObj,infIndexArray,weightList)
    wtList = list(weightList)
    totalTime = cmds.timerX(startTime=startTime)
    print("\nTotal Time: "+str(totalTime))
    return wtList
# -- set weight by selected joint * selected vertex
def setWeight_cust_OpenMaya(skinName, weightList, influenceList, componentList):
    startTime = cmds.timerX()
    # Get skinClusterFn
    skinClusterSel = OpenMaya.MSelectionList()
    skinClusterObj = OpenMaya.MObject()
    OpenMaya.MGlobal.getSelectionListByName(skinName,skinClusterSel)
    skinClusterSel.getDependNode(0,skinClusterObj)
    skinFn = OpenMayaAnim.MFnSkinCluster(skinClusterObj)
    # Get SkinCluster Influence List
    if type(influenceList) == str or type(influenceList) == unicode: influenceList = [str(influenceList)]
    influence_fullList = getBoneList_OpenMaya(skinName) # influence list and physical index
    infIndexArray = OpenMaya.MIntArray()
    [infIndexArray.append(influence_fullList.index(influence)) for influence in influenceList]
    # Build selection list
    selectionList = OpenMaya.MSelectionList()
    selectionPath = OpenMaya.MDagPath()
    selectionObj = OpenMaya.MObject()
    if type(componentList) == str or type(componentList) == unicode: componentList = [str(componentList)]
    [selectionList.add(i) for i in componentList]
    selectionList.getDagPath(0,selectionPath,selectionObj)
    # Build Master Weight Array
    wtArray = OpenMaya.MDoubleArray()
    oldWtArray = OpenMaya.MDoubleArray()
    [wtArray.append(i) for i in weightList]        
    # Set skinCluster weights
    skinFn.setWeights(selectionPath,selectionObj,infIndexArray,wtArray,False,oldWtArray) # normalize false
    totalTime = cmds.timerX(startTime=startTime)
    print("\nTotal Time: "+str(totalTime))
  • wip
  • work with all deformers, like non-linear, blendshape, lattice