Announcements

Between mid-October and November, the content on AREA will be relocated to the Autodesk Community M&E Hub and the Autodesk Community Gallery. Learn more HERE.

Can't get latest set data with callbacks.

Can't get latest set data with callbacks.

morinnicolas8944
Advocate Advocate
766 Views
2 Replies
Message 1 of 3

Can't get latest set data with callbacks.

morinnicolas8944
Advocate
Advocate

I need to use set data in my node to perform some computations. The set node being a separate node from mine, it is not allowed to get its data in compute(), so I set up callbacks to be notified of changes and recompute at that time. (I was inspired by this post to do so http://around-the-corner.typepad.com/adn/2016/12/use-a-node-as-input-for-your-custom-node.html).

However, when modifications (Smooth) are applied to the mesh whose edges are included in the set, I don't get the correct set data reflecting the modified mesh.

 

I attached the source code for a test node at the bottom of this post. With this node, we connect its Message output to the Set's UsedBy input to allow us to find the Set. We also have a mesh input connected to the output of InMesh in the mesh node. We use a node dirty callback on the test node to be notified when the mesh is modified (thanks to our mesh input). Similarly, we add nodeDirtyPlug and attributeChanged callbacks on the set node, when it is connected. Whenever any of these callbacks is called, we check the set data and output it in the Script Editor window.

 

I used the following MEL script to build a scene containing the node.

 

// Create a cube
$polys = `polyCube`;
$poly = $polys[0];
$shape = "pCubeShape1";

// Add one edge to a set
select -clear;
polySelect -shortestEdgePath 0 1 $poly;
$setName = `sets`;

// Create the test node
$testNode = `createNode "TestNode"`;

// Connect the attributes
connectAttr ($shape + ".inMesh") ($testNode + ".mesh");
connectAttr ($testNode + ".message") ($setName + ".usedBy[0]");

The callbacks are called a few times while building the scene, but the final call shows that edge [0, 1] is selected, which is correct.

 

Next, we select the cube created above and apply Mesh -> Smooth from Maya's menu. The callback for the mesh being dirty is called twice, but on the final call we find only 1 edge [0, 14] in the set data, yet the set now actually contains 2 edges. We notice that the callbacks for the set node being dirty were never called.

 

Finally, we force another callback by deleting the connection between the mesh and the mesh attribute on our node. Alternatively, we could also disconnect and reconnect the connection between our node's Message and the set node's usedBy to get a call to the attributeChanged callback we put on the set.

 

When this callback executes, we now find that the set data contains the 2 edges [0,14] and [14, 1] which would be the correct, up to date data after the smooth.

 

However, I can't find a way to get the correct updated data automatically, without having to artificially manipulate the node connections.

 

Any help would be appreciated.

 

Below is the code for the test node.

 

#include <maya/MCallbackIdArray.h>
#include <maya/MDagPath.h>
#include <maya/MFnMeshData.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnSet.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MGlobal.h>
#include <maya/MItMeshEdge.h>
#include <maya/MNodeMessage.h>
#include <maya/MPlugArray.h>
#include <maya/MPxNode.h>
#include <maya/MSelectionList.h>

class testNode : public MPxNode
{
public:
	testNode();
	virtual ~testNode();

	static void* creator();
	virtual void postConstructor() override;
	static MStatus initialize();

	MStatus connectionMade(const MPlug &plug, const MPlug &otherPlug, bool asSrc) override;
	MStatus connectionBroken(const MPlug &plug, const MPlug &otherPlug, bool asSrc) override;

	// local node attributes
	static MTypeId id;

	static MObject aMesh;

private:
	MCallbackIdArray callbackIds;

	void recompute();

	static void nodePreRemoveCallback(MObject &node, void *instance);

	static void dirtyCallback(MObject &node, MPlug &plug, void *instance);
	static void nodeDirtyCallback(MObject &node, void *instance);
	static void attrChangedCallback(MNodeMessage::AttributeMessage msg, MPlug &plug,
		MPlug &otherPlug, void *instance);
};

MTypeId     testNode::id(0x8000c);

// local attributes
MObject		testNode::aMesh;

testNode::testNode() {}
testNode::~testNode() {}

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

void testNode::postConstructor()
{
	MStatus status;

	MObject thisObj = thisMObject();

	MCallbackId cbId = MNodeMessage::addNodePreRemovalCallback(
		thisObj, nodePreRemoveCallback, this, &status);
	CHECK_MSTATUS(status);
	CHECK_MSTATUS(callbackIds.append(cbId));

	cbId = MNodeMessage::addNodeDirtyPlugCallback(thisObj, dirtyCallback, this, &status);
	CHECK_MSTATUS(status);
	CHECK_MSTATUS(callbackIds.append(cbId));
}

MStatus testNode::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);

	return MStatus::kSuccess;
}

MStatus testNode::connectionMade(const MPlug & plug, const MPlug & otherPlug, bool asSrc)
{
	MStatus status;

	// Message is connected to the set node
	if (plug == message && asSrc)
	{
		MObject setNode = otherPlug.node(&status);
		CHECK_MSTATUS_AND_RETURN_IT(status);
		if (setNode.apiType() != MFn::kSet) // Not a set!
		{
			return MPxNode::connectionMade(plug, otherPlug, asSrc);
		}

		// Add a node dirty callback
		MCallbackId cbId = MNodeMessage::addNodeDirtyPlugCallback(setNode,
			dirtyCallback, this, &status);
		CHECK_MSTATUS(status);
		CHECK_MSTATUS(callbackIds.append(cbId));

		// Add attribute changed callback
		cbId = MNodeMessage::addAttributeChangedCallback(setNode,
			attrChangedCallback, this, &status);
		CHECK_MSTATUS(status);
		CHECK_MSTATUS(callbackIds.append(cbId));

		return MStatus::kSuccess;
	}

	return MPxNode::connectionMade(plug, otherPlug, asSrc);
}

MStatus testNode::connectionBroken(const MPlug &plug, const MPlug &otherPlug, bool asSrc)
{
	// TODO: Remove callbacks for the disconnected node.
	return MPxNode::connectionBroken(plug, otherPlug, asSrc);
}

void testNode::nodePreRemoveCallback(MObject &node, void *instance)
{
	// Remove the callbacks
	testNode *thisObj = static_cast<testNode*>(instance);
	CHECK_MSTATUS(MMessage::removeCallbacks(thisObj->callbackIds));
}

void testNode::dirtyCallback(MObject &node, MPlug &plug, void *instance)
{
	MStatus status;

	testNode *thisPtr = static_cast<testNode*>(instance);
	MObject thisObj = thisPtr->thisMObject();

	if (node == thisObj && plug == aMesh)
	{
		MGlobal::displayInfo("Mesh input dirty.");
		thisPtr->recompute();
	}
}

void testNode::nodeDirtyCallback(MObject &node, void *instance)
{
	MStatus status;

	testNode *thisPtr = static_cast<testNode*>(instance);

	if (node.apiType() == MFn::kSet)
	{
		MGlobal::displayInfo("Set changed (node dirty).");
		thisPtr->recompute();
	}
}

void testNode::attrChangedCallback(MNodeMessage::AttributeMessage msg, MPlug &plug,
	MPlug &otherPlug, void *instance)
{
	MStatus status;

	testNode *thisPtr = static_cast<testNode*>(instance);

	MObject node = plug.node(&status);
	CHECK_MSTATUS(status);

	if (node.apiType() == MFn::kSet)
	{
		MGlobal::displayInfo("Set changed (attribute changed).");
		thisPtr->recompute();
	}
}

void testNode::recompute()
{
	MGlobal::displayInfo("Recompute");

	MStatus status;

	MObject thisObj = thisMObject();

	// Get the connected edge sets
	MPlug plug(thisObj, message);
	MPlugArray messageOuts;
	plug.connectedTo(messageOuts, false, true, &status);
	CHECK_MSTATUS(status);

	// Remove the nodes that are not sets from the results.
	for (int i = messageOuts.length() - 1; i >= 0; --i)
	{
		MPlug plug = messageOuts[i];
		MObject node = plug.node(&status);
		CHECK_MSTATUS(status);
		if (!node.hasFn(MFn::kSet))
		{
			// All array elements following the removed element are shifted toward the first element.
			CHECK_MSTATUS(messageOuts.remove(i));
		}
	}

	unsigned int nbConn = messageOuts.length();
	// Support only 1 set for testing
	if (nbConn != 1)
	{
		cerr << "\nError: Only supports 1 connected edge set.\n";
		return;
	}

	MObject setObject = messageOuts[0].node(&status);
	CHECK_MSTATUS(status);
	MFnSet fnSet(setObject);
	MSelectionList selectionList;
	CHECK_MSTATUS(fnSet.getMembers(selectionList, true));

	// We also support only 1 shape in the set.
	if (selectionList.length() != 1)
	{
		cerr << "\nError: Selection list contains " << selectionList.length() << " items. Expecting 1!\n";
		return;
	}

	MDagPath item;
	MObject component;
	selectionList.getDagPath(0, item, component);

	// Edge
	if (component.hasFn(MFn::kMeshEdgeComponent)) {
		MItMeshEdge edgeFn(item, component, &status);
		CHECK_MSTATUS(status);
		for (; !edgeFn.isDone(); edgeFn.next()) {
			MGlobal::displayInfo(MString(" Edge [") + edgeFn.index(0) + "," + edgeFn.index(1) + "]");
		}
	}
}

// standard initialization procedures
//

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

	return result;
}

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

 

0 Likes
Accepted solutions (1)
767 Views
2 Replies
Replies (2)
Message 2 of 3

cheng_xi_li
Autodesk Support
Autodesk Support
Accepted solution

Hi,

 

I am glad somebody is actually reading my blog with questions:)

 

I doubt set is using any attributes to store its member, please try MObjectSetMessage instead.

 

Yours,

Li

0 Likes
Message 3 of 3

morinnicolas8944
Advocate
Advocate

Thank you for the solution. It works perfectly. I hadn't noticed the Set callback.

 

And I regularly find really useful information and tricks on your blog. Thanks for that too 🙂

0 Likes