Using setDependentsDirty to dirty only an element of an output array

Using setDependentsDirty to dirty only an element of an output array

Anonymous
Not applicable
1,886 Views
3 Replies
Message 1 of 4

Using setDependentsDirty to dirty only an element of an output array

Anonymous
Not applicable

Hi,

 

I am having some issues trying to dirty only one element (at a specific logical index) of an output array. More precisely, I have a custom MPxDeformer node which has a multi compound attribute as an input, and I want to only dirty a specific element of an output array attribute, e.g., modifying the specific attribute inputArray[2].inputMesh should only dirty outputArray[2], not outputArray[0] and outputArray[1]. To do so, I have overriden setDependentsDirty, which works in most cases.  However, I found that when I load a file with my deformer already setup that method is not called unless I manually change the input value, thus my output is not computed. Am I trying to do something that setDependentsDirty is not meant to be for? Is there a better way to accomplish what I want to do?

I attached a simple test I did that also has that issue. Here's how I create my attributes:

MStatus TestArrayPlugsNode::initialize()
{
    MFnCompoundAttribute compoundAttribute;
    MFnTypedAttribute typedAttribute;
    MFnNumericAttribute numericAttribute;

    _inputArray = compoundAttribute.create("inputArray", "in");
    compoundAttribute.setArray(true);

    _inputMesh = typedAttribute.create("inputMesh", "inMesh", MFnData::kMesh);
    compoundAttribute.addChild(_inputMesh);

    _inputValue = numericAttribute.create("inputValue", "inValue", MFnNumericData::kDouble, 1.0);
    compoundAttribute.addChild(_inputValue);

    _outputArray = typedAttribute.create("outputArray", "out", MFnData::kMesh);
    typedAttribute.setArray(true);
    typedAttribute.setWritable(false);
    typedAttribute.setStorable(false);

    addAttribute(_inputArray);
    addAttribute(_outputArray);

    return MS::kSuccess;
}


And here's my setDependentsDirty implementation (although it should not be the problem since it is not called at all):

MStatus TestArrayPlugsNode::setDependentsDirty(const MPlug& plug, MPlugArray& affectedPlugs)
{
    std::cout << "setDependentsDirty(" << plug.name() << ")" << std::endl;

    if (plug.attribute() == _inputMesh)
    {
        MPlug outputArrayPlug(thisMObject(), _outputArray);
        MPlug outputArrayElemPlug = outputArrayPlug.elementByLogicalIndex(plug.parent().logicalIndex());

        affectedPlugs.append(outputArrayElemPlug);
    }
    else
    {
        return MPxDeformerNode::setDependentsDirty(plug, affectedPlugs);
    }

    return MS::kSuccess;
}


Thanks,

1,887 Views
3 Replies
Replies (3)
Message 2 of 4

bradley_henke
Enthusiast
Enthusiast

I don't claim to have all of the answers here, but I do have a couple of observations for you:

  1. If you use "setDependentsDirty" with a multi-compound plug attribute, you need to dirty all ancestor plugs as well as descendant plugs. The C++ docs for this method are more complete than the python docs and they give an example of this. 
  2. The Evaluation Manager creates an Evaluation Graph using dirty propagation, so it will call setDependentsDirty when it is initially constructing the Evaluation Graph. When it does this, it will probably request the root plug (the array plug) rather than the individual element plugs (which is why it's important to have the parent plug dirtied).
0 Likes
Message 3 of 4

bradley_henke
Enthusiast
Enthusiast

I had a chance to do a bit more digging on this one and I discovered something interesting, which could be related to your problem. Since all of the dependencies are defined in setDependentsDirty(), rather than using attributeAffects(), the initial dirty propagation only happens after you change the value of the input attribute. This looks to be the case with all attributes, not just arrays.

 

For example,

I tried creating an "inputNumber" double attribute which affects an "outputNumber" double attribute, using setDependentsDirty(). When my node is created, the outputNumber is set to its default value and it's not dirty. Therefore doing a getAttr on the outputNumber attribute returns the default value (rather than the computed value which depends on inputNumber). It's not until you change the inputNumber attribute that the dirty propagation occurs and setDependentsDirty() is called, which then dirties outputNumber. Once this happens, then subsequent requests for outputNumber work as expected.

 

This is NOT the case if the affects relationship is set with attributeAffects(). In this case, the very first time that I request the outputNumber, it is calculated and correctly returned. So, it seems the problem is that maya does not initially call setDependentsDirty() when the node is created.

 

Workaround

This is a bit of a hack, but it worked for me. My suggestion is to create a hidden dummy attribute whose sole purpose is to dirty all of the output attributes when the node is initially created. In your node's constructor you can do something like this:

addNodeCallbackId = MDGMessage::addNodeAddedCallbback( [](MObject &node, void *clientData) {
    MyNodeType *myNode = static_cast<MyNodeType*>( clientData );
    MFnDependencyNode nodeFn( myNode->thisMObject );
    MPlug inPlug = nodeFn.findPlug( _dummyAttr, false );
    inPlug.setBool( true );
    MMessage::removeCallback( myNode->addCallbackId );
    myNode->addNodeCallbackId = 0
}, "myNodeType", this);

 

Message 4 of 4

bradley_henke
Enthusiast
Enthusiast

However, as I mentioned previously, in setDependentsDirty() you should be adding the array plug as well. If you end up animating something on your one of your input meshes connecting it to time, then it will be added to the Evaluation Graph, and the evaluation graph will call compute on the array plug which will expect to you to update all output meshes. If you don't add the array plug as a dependency, your animation will not update correctly.

 

One other quick side note,

It looks like you are inheriting from MPxDeformer, but then you create plugs for you input meshes. The whole point of using MPxDeformer is to allow maya to set up the deformer connections for you when you use the mel "deformer" command. When that is done, the native input plugs for the geometry will be used (you can find them on MPxGeometryFilter). Additionally, the deformer will have a weight map which you can set to be paintable. You can still override compute on a deformer, rather than using the deform() function, if that suits you better, but at your node will work with all of the native deformer feature of maya.