OpenMaya - how to properly get/set weights

Hi, I have a problem understanging the getWeights() and setWeights() methods of MFnSkinCluster. According to the docs (MFnSkinCluster) you’re supposed to provide MDagPath to the shape node and an MObject of components to get/set weights for. The name ‘components’ suggests you can provide more than one which I don’t know how to do.

The way I’ve beed doing it so far is by using MItMeshVertex to get/set weights on single vertices in an iteration, which is terribly slow.

My typical approach looks like this:

import maya.api.OpenMaya as om
import maya.api.OpenMayaAnim as oma
import json

# the file provides a float[i][j] array 
# where i = vertex index and j = influence index
file_path = 'path/to/a/file.json'


def set_weights_by_index():
    
    # get weights from file
    with open(file_path, 'r') as file_in:
        weights = json.load(file_in)


    # find shape node and create the iterator
    sel_list = om.MGlobal.getActiveSelectionList()
    sel_dag_path = sel_list.getDagPath(0)
    shape_node_dag = sel_dag_path.extendToShape()
    vert_iterator = om.MItMeshVertex(shape_node_dag)
    

    # traverse the DG to find skin cluster node
    shape_node = shape_node_dag.node()
    dg_iterator = om.MItDependencyGraph(
                                        shape_node,
                                        om.MFn.kSkinClusterFilter,
                                        om.MItDependencyGraph.kUpstream,
                                        om.MItDependencyGraph.kPlugLevel)
    
    while not dg_iterator.isDone():
        current_node = dg_iterator.currentNode()

        if not current_node.isNull():
            skin_cluster_node = current_node
        dg_iterator.next()
    

    # iterate over vertices to set the weights
    skin_cluster_fn = oma.MFnSkinCluster(skin_cluster_node)
    while not vert_iterator.isDone():
        current_index = vert_iterator.index()
        weights_to_set = om.MDoubleArray(weights[current_index])
        influence_indices = om.MIntArray([i for i in range(0, len(weights_to_set))])

        skin_cluster_fn.setWeights(
                                   shape_node_dag, 
                                   vert_iterator.currentItem(), 
                                   influence_indices, 
                                   weights_to_set, 
                                   True, 
                                   False)
        vert_iterator.next()


set_weights_by_index()

As you can see I’m getting the current vertex with MItMeshVertex.currentItem() and just setting the weights one by one. I’m pretty sure this is not the way to do this, but I have no idea how to get a single MObject representing a whole set of vertices. Does anyone have experience with this?

My experiences with MFnSkinCluster have been uniformly terrible, so I just iterate over the weight array plugs directly. You can use setAncestorLogicalIndex on the leaf weight plug to iterate over the parents even faster - use physical indices to work with the plugs, and use the logical indices as keys in your weight dict.

Usually my output is something like
{ vertex index : { influence index : influence value } }
to me it’s just most intuitive, since we’ll probably have fewer joints than vertices on our mesh. You can also use a numpy array for the same thing, but that can get expensive unless you go into the sparse systems

I have never worked with MFnSkinCluster, but I can answer your question about getting a single MObject representing a whole set of vertices.

One way to do it is by conversion from a text string. For example:

sel_list = om.MSelectionList()
sel_list.add('pCubeShape1.vtx[*]')
# dag_path will contain an MDagPath for the shape.
# components will contain an MObject for the components.
dag_path, components = sel_list.getComponent(0)

Another way to do it is to create the object manually using MFnSingleIndexedComponent:

vertex_list_fn = om.MFnSingleIndexedComponent()
# The following line actually creates the MObject.  Pass the correct type to the 
# "create" function to let Maya know we want vertex components.
vertex_components = vertex_list_fn.create(om.MFn.kMeshVertComponent)
# Now add the vertex indices.  Note that indeces could be a list of selected items,
# like [2, 3, 4, 7, 14, 15].  Or it could be all of the vertices, like
# "range(num_vertices)".
# Also note that "addElements" is run on "vertex_list_fn" and not "vertex_components".
vertex_list_fn.addElements(indeces)
1 Like

Yes, I’ve finally found the solution too. Nice thing is MFnSkinCluster starts to be quite convenient and compact once you have it figured out. Now I’m struggling with removing single element from MFnSingleIndexedComponent (adding is as easy ass calling the .add(int) method). I think I’ll temporarily work around it by just removing from list and creating a new component.
Anyway, thanks for the answer, your second example actually validated an idea I planned to check :wink: