Set data in MPxObjectSet node does not match mesh data from added mesh attribute

Set data in MPxObjectSet node does not match mesh data from added mesh attribute

morinnicolas8944
Advocate Advocate
721 Views
3 Replies
Message 1 of 4

Set data in MPxObjectSet node does not match mesh data from added mesh attribute

morinnicolas8944
Advocate
Advocate

This is a follow-up to this question.
https://forums.autodesk.com/t5/maya-programming/getting-set-data-in-node-s-compute-fails-when-loadin...

I have a node derived from MPxObjectSet so it can contain information about a set of edges on a mesh. The information is used in compute() to create UV maps for that mesh. For this, my node adds a mesh attribute to the attributes of MPxObjectSet, which is the mesh on which we will work.

When created, the node is inserted just before the final shape in the mesh history and we add the edge set information to it. We then use the set data to find the polygon vertices matching the selected edges. This works fine in simple situations, but if we add, for example, a smooth operation on the polygon, the edge indexes stop matching the polygon vertices we get from the mesh attribute.

I would like to know if there is a way to reconcile the index data from the set with the vertex data from the mesh attribute when the polygon has been smoothed.

I wrote a test node to isolate and illustrate the problem. The code is included at the end of this post and does not include the workaround for the problem when opening a file discussed in the question I asked last week.

The problem can be reproduced by following these steps after loading the test plugin.

We run the following MEL script that creates a cube and an instance of our test node. It also selects an edge on the cube and adds it to the test node (since it is also a set node). Finally, it inserts the test node just before the final shape in the mesh history.

Test script:
-----------
proc string getNodeName(string $nodeAttr)
{
    string $buffer[];
    tokenize($nodeAttr, ".", $buffer);
    return $buffer[0];
}

$polys = `polyCube`;
$poly = $polys[0];
select -clear;
polySelect -shortestEdgePath 0 1 $poly;

// Create the test node
$setName = `sets`;
$nodeName = `createNode "setNodeTest"`;
$edges = `sets -q $setName`;
sets -e -addElement $nodeName $edges;

// Add the test node in the path for the mesh
$shape = `connectionInfo -sourceFromDestination ($nodeName + ".dagSetMembers[0]")`;
$shape = `getNodeName $shape`;
$meshsrc=`connectionInfo -sourceFromDestination ($shape + ".inMesh")`;
connectAttr $meshSrc ($nodeName + ".mesh");
connectAttr -force ($nodeName + ".mesh") ($shape + ".inMesh");
-----------

The resulting node network is as shown below (some unrelated nodes are not included to remove clutter).

 

TestNode1.JPG

The test node outputs the data from the set information of the node and the mesh attribute to the Output Window during compute() as follows:

----
Polygon 0 vertices:
 0,1,3,2,
Polygon 1 vertices:
 2,3,5,4,
Polygon 2 vertices:
 4,5,7,6,
Polygon 3 vertices:
 6,7,1,0,
Polygon 4 vertices:
 1,7,5,3,
Polygon 5 vertices:
 6,0,2,4,
Edge idx= [0,1]
----

We can see that the edge between vertices 0 and 1 is selected, which seems to be OK.

Next, we select the cube and execute "smooth" from the "Mesh" menu. This adds the node "polySmoothFace1" between our test node and the cube in the path of the mesh.

The new node network is shown below.

TestNode2.JPG

 

We disconnect the mesh input of our test node and then reconnect it to force an execution of its compute().

This time, the output is as follows.

----
Polygon 0 vertices:
 0,1,3,2,
Polygon 1 vertices:
 2,3,5,4,
Polygon 2 vertices:
 4,5,7,6,
Polygon 3 vertices:
 6,7,1,0,
Polygon 4 vertices:
 1,7,5,3,
Polygon 5 vertices:
 6,0,2,4,
Edge idx= [0,14]
Edge idx= [14,1]
----

So the set data now contains 2 edges connected at vertex 14. But our mesh data does not contain a vertex 14, which is our problem.

My guess is that the set data reflects the mesh after applying the smooth operation, while the mesh attribute data is the data before smooth, so there is a discrepancy. Is there a way to reconcile the data?

 

Thanks!

 

Source code for the test node.

 

#include <maya/MDagPath.h>
#include <maya/MFnMesh.h>
#include <maya/MFnMeshData.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnSet.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MItMeshEdge.h>
#include <maya/MItSelectionList.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));

	MFnMesh fnMesh(outputData.asMesh(), &status);
	CHECK_MSTATUS_AND_RETURN_IT(status);

	int numPolys = fnMesh.numPolygons(&status);
	CHECK_MSTATUS_AND_RETURN_IT(status);
	MIntArray vertexList;
	for (int pIndex = 0; pIndex < numPolys; ++pIndex) {
		cerr << "Polygon " << pIndex << " vertices:\n";
		fnMesh.getPolygonVertices(pIndex, vertexList);
		unsigned int nbV = vertexList.length();
		cerr << " ";
		for (unsigned int i = 0; i < nbV; ++i)
		{
			cerr << vertexList[i] << ",";
		}
		cerr << "\n";
	}

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

	for (MItSelectionList iter(selectionList); !iter.isDone(); iter.next())
	{
		MDagPath item;
		MObject component;
		iter.getDagPath(item, component);

		// Edge
		if (component.hasFn(MFn::kMeshEdgeComponent))
		{
			MItMeshEdge edgeFn(item, component, &status);
			CHECK_MSTATUS(status);
			for (; !edgeFn.isDone(); edgeFn.next()) {
				cerr << "Edge idx= [" << edgeFn.index(0) << "," << edgeFn.index(1) << "]\n";
			}
		}
		else
		{
			cerr << "Component is not an edge!\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
Accepted solutions (1)
722 Views
3 Replies
Replies (3)
Message 2 of 4

kevin.picott
Alumni
Alumni

I think I can explain what's going on here. In your compute method your are taking vertex data from one mesh and edge data from another mesh.

 

For the vertex data you get your MFnMesh from the aMesh attribute of the plugin node. That's the mesh feeding into the smooth mesh, so it will be the pre-smoothed data.

 

When you get your edge data, you get your MFnMesh from the set member, which is pCubeShape1, which in turn gets its input from the smooth mesh node's output. To reconcile your data just pick one of the two meshes, depending on what your plugin needs to do with it, and use that data.

 

Here's an example where I've modified your code to add the vertex output to the post-smoothed mesh from which the edge data is taken:

 

#include <maya/MDagPath.h>
#include <maya/MFnMesh.h>
#include <maya/MFnMeshData.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnSet.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MItMeshEdge.h>
#include <maya/MItSelectionList.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;
	}

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

	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));

	MFnMesh fnMesh(outputData.asMesh(), &status);
	CHECK_MSTATUS_AND_RETURN_IT(status);

	int numPolys = fnMesh.numPolygons(&status);
	CHECK_MSTATUS_AND_RETURN_IT(status);
	MIntArray vertexList;
	for (int pIndex = 0; pIndex < numPolys; ++pIndex) {
		cerr << "Polygon " << pIndex << " vertices:\n";
		fnMesh.getPolygonVertices(pIndex, vertexList);
		unsigned int nbV = vertexList.length();
		cerr << " ";
		for (unsigned int i = 0; i < nbV; ++i)
		{
			cerr << vertexList[i] << ",";
		}
		cerr << "\n";
	}

	for (MItSelectionList iter(selectionList); !iter.isDone(); iter.next())
	{
		MDagPath item;
		MObject component;
		iter.getDagPath(item, component);
		cerr << "Selected item " << item.fullPathName().asChar() << "\n";

		// Edge
		if (component.hasFn(MFn::kMeshEdgeComponent))
		{
			MItMeshEdge edgeFn(item, component, &status);
			CHECK_MSTATUS(status);
			for (; !edgeFn.isDone(); edgeFn.next()) {
				cerr << "Edge idx= [" << edgeFn.index(0) << "," << edgeFn.index(1) << "]\n";
			}

			MFnMesh fnMesh(item, &status);
			CHECK_MSTATUS_AND_RETURN_IT(status);

			int numPolys = fnMesh.numPolygons(&status);
			CHECK_MSTATUS_AND_RETURN_IT(status);
			MIntArray vertexList;
			for (int pIndex = 0; pIndex < numPolys; ++pIndex) {
				cerr << "Polygon " << pIndex << " vertices:\n";
				fnMesh.getPolygonVertices(pIndex, vertexList);
				unsigned int nbV = vertexList.length();
				cerr << " ";
				for (unsigned int i = 0; i < nbV; ++i)
				{
					cerr << vertexList[i] << ",";
				}
				cerr << "\n";
			}
		}
		else
		{
			cerr << "Component is not an edge!\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;
}


Kevin "Father of the DG" Picott

Senior Principal Engineer
Message 3 of 4

morinnicolas8944
Advocate
Advocate

Thanks! I tried your solution and it works well.

 

I just want to make sure it's safe to use the MFnMesh initialized with the MDagPath since we are in compute() and this is not data we got from the MDataBlock. Actually, I'm not even sure whether I am doing something not allowed when I use

MFnSet fnSet(thisMObject());

But if that's not allowed, how would it be possible to get set data in compute() since we can't pass MObjects as attributes.

0 Likes
Message 4 of 4

kevin.picott
Alumni
Alumni
Accepted solution

You are correct, especially since the mesh node is upstream from the set node so it will trigger all sorts of bad evaluation paths.

 

The problem with using the set node is that it's meant to be providing membership information, not the actual members. So to answer your second question, yes MFnSet creation is perfectly legal.

 

Unfortunately the representation is the same so the natural thing to conclude is that you can use the members as you normally would. Walking the selected members for inspection is okay, casting them to MFnMesh and calling methods on them is not.

 

So how do you accomplish what you're after? Overriding MPxObjectSet gives you the ability to affect set members - you want something that gives you the ability to override geometry data. Your best bet is MPxSurfaceShape. Here's a good starting point on dealing with geometry modifications - http://help.autodesk.com/view/MAYAUL/2018/ENU/?guid=__files_Shapes_Shape_classes_htm



Kevin "Father of the DG" Picott

Senior Principal Engineer