[Maya C++ API] Set skinning weight attributes

Some C++ Maya API code to set skin weights (multi attributes) of a skin cluster node.

Setting skin weights (multi attribute / array attribute) with Mplug. Intermediate speed

void set_skinning_weights(
        MObject skin_cluster_node,
        const std::vector< std::map<int/*joint id*/, float/*joint weight*/> >& weights)
{
    MStatus status;
    MFnDependencyNode deformer_node(skin_cluster_node, &status);
    mayaCheck(status);
    MPlug weight_list_plug = deformer_node.findPlug("weightList", true, &status);
    mayaCheck(status);
    for( unsigned i = 0; i < weight_list_plug.numElements(); i++ )// For each vertex
    {
        // weightList[i]
        MPlug ith_weights_plug = weight_list_plug.elementByPhysicalIndex( i );

        // weightList[i].weight
        MPlug plug_weights = ith_weights_plug.child(0); // access first compound child

        // First reset values to zero:
        int nb_weights = plug_weights.numElements();
        for( int j = 0; j < nb_weights; j++ ) { // for each joint
            MPlug weight_plug = plug_weights.elementByPhysicalIndex( j );
            // weightList[i].weight[j]
            mayaCheck( weight_plug.setValue( MObject() ) );
        }

        for(const std::pair<anim::bone::Id, float>& value : weights[i]) {
            MPlug weight_plug = plug_weights.elementByLogicalIndex( value.first );

            // weightList[i].weight[value.second]
            mayaCheck( weight_plug.setValue( value.second ) );
        }
    }
}

The fastest way to set multi attributes / array attributes is done through a DAG node. (100 times faster than MPlugs!)

void set_skinning_weights(
        const std::vector<std::map<int/*joint id*/, float/*skin weight*/> >& weights,
        MDataBlock& block)
{
    MStatus status = MS::kSuccess;
    MArrayDataHandle array_hdl = block.outputArrayValue(_s_skin_weights, &status);
    mayaCheck(status);
    for(unsigned i = 0; i < weights.size(); i++)
    {
        mayaCheck( array_hdl.jumpToArrayElement( i ) );

        // weightList[i]
        MDataHandle element_hdl = array_hdl.outputValue( &status ); 
        mayaCheck(status);
        // weightList[i].weight
        MDataHandle child = element_hdl.child( _s_per_joint_weights );

        MArrayDataHandle weight_list_hdl(child, &status);
        mayaCheck(status);

        MArrayDataBuilder weight_list_builder = weight_list_hdl.builder(&status);
        mayaCheck(status);

        unsigned handle_count = weight_list_hdl.elementCount(&status);
        mayaCheck(status);

        unsigned builder_count = weight_list_builder.elementCount(&status);
        mayaCheck(status);
        mayaAssert( builder_count == handle_count);

        std::map<int/*influence obj id / joint id*/, float> map = weights[i];

        std::vector to_remove;
        to_remove.reserve( map.size() );

        // Scan array, update existing element, remove unsused ones
        for(unsigned j = 0; j < handle_count; ++j)
        {
            // weightList[i].weight[j]
            mayaCheck( weight_list_hdl.jumpToArrayElement(j) );
            unsigned index = weight_list_hdl.elementIndex(&status);
            mayaCheck(status);

            auto elt = map.find( index );

            if( elt != map.end() )
            {
                MDataHandle hdl = weight_list_builder.addElement(index, &status);
                mayaCheck(status);
                hdl.setDouble( (double)elt->second );
                map.erase( elt );
            }
            else
            {
                to_remove.push_back( index );
            }
        }

        for( unsigned idx : to_remove ){
            mayaCheck( weight_list_builder.removeElement( idx ) );
        }

        mayaCheck( weight_list_hdl.set( weight_list_builder ) );
    }

}

MStatus Custom_node::initialize()
{
    // Initialize skin weights multi attributes
    MFnNumericAttribute numAtt;
    _s_per_joint_weights = numAtt.create("per_joint_weights", "jw", MFnNumericData::kDouble, 0.0, &status);
    mayaCheck(status);
    mayaCheck(numAtt.setKeyable(false));
    mayaCheck(numAtt.setArray(true));
    mayaCheck(numAtt.setReadable(true) );
    mayaCheck(numAtt.setWritable(true) );//when true skin weights will be saved on file
    mayaCheck(numAtt.setUsesArrayDataBuilder(true) );
    mayaCheck(addAttribute(_s_per_joint_weights));

    MFnCompoundAttribute cmpAttr;
    _s_skin_weights = cmpAttr.create("skin_weight_list", "sw", &status);
    mayaCheck(status);
    mayaCheck(cmpAttr.setArray(true));
    mayaCheck(cmpAttr.addChild(_s_per_joint_weights));
    mayaCheck(cmpAttr.setKeyable(false));
    mayaCheck(cmpAttr.setReadable(true));
    mayaCheck(cmpAttr.setWritable(true));
    mayaCheck(cmpAttr.setUsesArrayDataBuilder(true));
    mayaCheck(addAttribute(_s_skin_weights) );
}

// Don't forget to initialize and connect your attributes:
MPlug get_plug(const MObject& node, const MObject& attribute)
{
    MStatus status;
    MFnDependencyNode dg_fn ( node );
    MPlug plug = dg_fn.findPlug ( attribute, true, &status );
    mayaCheck(status);
    return plug;
}

std::vector< int /*bone id*/, float /*vertex weight*/> > _skin_weights;

for(unsigned vidx = 0; vidx < _skin_weights.size(); ++vidx)
{
    // weightList[i]
    MPlug weightList_elt = weight_list_plug.elementByLogicalIndex(vidx, &status);
    mayaCheck(status);
    MPlug child = weightList_elt.child(0, &status); // access first compound child
    mayaCheck(status);
    for(const std::pair<int, float>& value : _skin_weights[vidx])
    {
        MPlug weight_plug = child.elementByLogicalIndex( value.first, &status );
        mayaCheck(status);

        // weightList[i].weight[value.second]
        mayaCheck( weight_plug.setDouble( 0.0 ) );
    }
}

MPlug weight_list_skin_clus = get_plug( _skin_cluster, "weightList");
MPlug weight_list_plug = get_plug( this->thisMObject(), _s_skin_weights);
MDGModifier dg;
mayaCheck( dg.connect(weight_list_plug, weight_list_skin_clus) );
mayaCheck( dg.doIt() );

Final note: on large meshes just disconnecting the compound attribute _s_skin_weights can be 5 times longer than actually setting the weights through set_skinning_weights(). It is best to keep the disconnection as minimal as possible.

No comments

(optional field, I won't disclose or spam but it's necessary to notify you if I respond to your comment)
All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.
Anti-spam question: