Announcements

The Autodesk Community Forums has a new look. Read more about what's changed on the Community Announcements board.

Getting set data in node's compute() fails when loading a saved file.

morinnicolas8944
Advocate

Getting set data in node's compute() fails when loading a saved file.

morinnicolas8944
Advocate
Advocate

Hello,

First sorry, this will be a bit long.

I am trying to write a kind of deformer node that acts on a set of edges instead of a set of vertexes. However, I run into problems with reading the contents of the set in some situations.

I tried many ways to work around the problem and my last attempt makes my node class be derived from MPxObjectSet so I can keep the set data inside the node itself. I will use a simplified version of this node as sample code to illustrate the issue. The node is called "setNodeTest". It appears that the forum won't let me attach a .cpp file, so I copied the source code at the end of the post.

The node adds a single attribute to the ones included in MPxObjectSet. That attribute is called "mesh" and it is the mesh on which we would need to work with the actual node. The sample test node does not actually do anything with the mesh.

What the test node does is it gets the selection list from the set data in the node and outputs its length in the Maya output window, in its compute() function.

Following are the steps we used to test and reproduce the problem in Maya 2017. This is all done with the Maya UI.

We create a polygon. Let's say a sphere.

We select a few edges on the sphere.

We run the following MEL script which creates a set containing the currently selected edges, creates an instance of our test node and adds the selected edges to our test node.
----------
string $setName = `sets`;
string $nodeName = `createNode "setNodeTest"`;
$edges = `sets -q $setName`;
sets -e -addElement $nodeName $edges;
----------

We open the Node Editor and show the connections for our test node (setNodeTest1) and the sphere (pShereShape1).

We reroute the input mesh of pSphereShape1 so that setNodeTest1 is inserted just before pSphereShape1 in he path of the mesh. We show this in the image below where we hid a few nodes to show only the ones we are interested in.

 

meshPath.JPG

Connecting the mesh attribute in the test node should have run its compute function which should have displayed "selectionList.length=1" in the Output Window, which is the expected value.

We save the scene.

We reload the scene.

Again compute() is executed and displays the expected result.

We select the sphere.

We execute "smooth" from the "Mesh" menu. This adds the node "polySmoothFace1" between our test node and the sphere in the path of the mesh.

As a test, we disconnect the mesh input of our test node and then reconnect it to force an execution of its compute(). Again, we get "selectionList.length=1" which is the expected value.

We save the scene again and reload it.

This time, when compute() is executed, it outputs "selectionList.length=0" meaning that we failed to get the set data.

This is our problem. We can't get correct set data if we smooth polygons after adding our node and then save and reload the scene.

 

Source code for the test node.

 

==============================================

#include <maya/MFnMeshData.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnSet.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MPlugArray.h>
#include <maya/MPxObjectSet.h>
#include <maya/MSelectionList.h>

class setNode : public MPxObjectSet
{
public:
    setNode();
    virtual ~setNode();

    static void* creator();
    static MStatus initialize();

    MStatus compute(const MPlug &plug, MDataBlock &block) override;

    // local node attributes
    static MTypeId id;

    static MObject aMesh;
};

MTypeId     setNode::id(0x8000c);

// local attributes
MObject        setNode::aMesh;

setNode::setNode() {}
setNode::~setNode() {}

void* setNode::creator()
{
    return new setNode();
}

MStatus setNode::initialize()
{
    MStatus status;
    MFnTypedAttribute attrFn;

    aMesh = attrFn.create("mesh", "mes", MFnMeshData::kMesh, MObject::kNullObj,
        &status);
    CHECK_MSTATUS(status);
    CHECK_MSTATUS(addAttribute(aMesh));

    attributeAffects(aMesh, aMesh);
    attributeAffects(dagSetMembers, aMesh);
    attributeAffects(DNSetMembers, aMesh);
    attributeAffects(partition, aMesh);
    attributeAffects(groupNodes, aMesh);

    return MStatus::kSuccess;
}

MStatus setNode::compute(const MPlug &plug, MDataBlock &block)
{
    MStatus status;

    if (plug != aMesh)
    {
        return MS::kUnknownParameter;
    }
    
    MPlugArray arr;
    plug.connectedTo(arr, true, false);
    if (arr.length() == 0)
    {
        // No input mesh. Nothing to do.
        // Necessary because it looks like we are being called once before the mesh is connected
        // to the node.
        CHECK_MSTATUS(block.setClean(plug));
        return MS::kSuccess;
    }

    MDataHandle inputData = block.inputValue(aMesh, &status);
    CHECK_MSTATUS_AND_RETURN_IT(status);

    MDataHandle outputData = block.outputValue(aMesh, &status);
    CHECK_MSTATUS_AND_RETURN_IT(status);

    CHECK_MSTATUS(outputData.copy(inputData));

    MFnSet fnSet(thisMObject());
    MSelectionList selectionList;
    CHECK_MSTATUS_AND_RETURN_IT(fnSet.getMembers(selectionList, true));

    unsigned int l = selectionList.length(&status);
    CHECK_MSTATUS(status);

    cerr << "\nselectionList.length=" << l << "\n";

    outputData.setClean();

    return MS::kSuccess;
}

// standard initialization procedures
//

MStatus initializePlugin( MObject obj )
{
    MStatus result;
    MFnPlugin plugin( obj, PLUGIN_COMPANY, "0.0", "Any");
    result = plugin.registerNode("setNodeTest", setNode::id, setNode::creator,
        setNode::initialize, MPxNode::kObjectSet);

    return result;
}

MStatus uninitializePlugin( MObject obj)
{
    MStatus result;
    MFnPlugin plugin( obj );
    result = plugin.deregisterNode(setNode::id);
    return result;
}

==============================================

0 Likes
Reply
Accepted solutions (1)
693 Views
3 Replies
Replies (3)

kevin.picott
Alumni
Alumni
Accepted solution

This is a tricky one, caused by some internal optimizations while loading a file.

 

The problem: the members() function of the set bails out early while reading a file since it doesn't believe the correct membership information is available yet. In your case it actually is, I'll be filing that as an improvement request internally - is it okay with you if I use your sample code to illustrate the problem?

 

The reason you only see it after rewiring is that the meshes make a special post-read evaluation request that gets them ready to draw and you've inserted your node into the path of that request.

 

Here's a possible approach to fix the problem, without relying on us to make the members() calculation smarter:

 

  • Check MFileIO::isOpeningFile() in your compute method, bailing early if it's true.
  • When that happens add in an MSceneMessage callback on the kAfterFileRead condition that will do the actual compute for you.


Kevin "Father of the DG" Picott

Senior Principal Engineer

morinnicolas8944
Advocate
Advocate

Thank you Kevin!

 

I had to change kAfterFileRead to kAfterOpen otherwise the callback was not called when opening the current file, but it was only called later, for the current file, when opening the next file. I'm not sure why. Anyway, with kAfterOpen your workaround seems to be working well.

 

Am I right in assuming that it's not safe to pass the MPlug and MDataBlock received by compute() to be used by the callback function?

 

I had some other issues later in the processing which might have been related to this problem, so I'll try the fix in the real code to see if it fixes everything and might come back with another question if that's not the case.

 

 is it okay with you if I use your sample code to illustrate the problem?

No problem at all. I'm glad if it can help 🙂

 

Nicolas

0 Likes

kevin.picott
Alumni
Alumni

Your assumption is correct. While it's true that usually those objects would stick around there is no guarantee so it's always safer to fetch them again.

Glad the solution helped, feel free to tag me if you have a follow-up.



Kevin "Father of the DG" Picott

Senior Principal Engineer
0 Likes