Announcements
Attention for Customers without Multi-Factor Authentication or Single Sign-On - OTP Verification rolls out April 2025. Read all about it here.

Compound attribute does not dirty plug

Anonymous

Compound attribute does not dirty plug

Anonymous
Not applicable

I have a node with an outPlug, which is an compound attribute of two array attributes. The arrays are Int and float2.

The compound attribute is connected to some other node, which itself has a shape as output. I added a "recompute" checkbox to the first node as input, so I can dirty an input from the attribute editor.

 

But with the compoundattribute as output the compute method is not called at all. With an mesh as output it works as expected and the checkbox triggers the compute method.

 

My attributes are defined like this:

 

outTriangleCoordinates = cAttr.create("outCoordinates", "oc", &status);
cAttr.setHidden(true); outTriangleIndex = nAttr.create("triangleIndex", "ti", MFnNumericData::kInt, 0, &status); nAttr.setArray(true); nAttr.setUsesArrayDataBuilder(true); nAttr.setWritable(false); cAttr.addChild(outTriangleIndex); outTriangleBarycentric = nAttr.create("triangleBarycentric", "bc", MFnNumericData::k2Float, 0, &status); nAttr.setArray(true); nAttr.setUsesArrayDataBuilder(true); nAttr.setWritable(false); cAttr.addChild(outTriangleBarycentric); recomputeAttr = nAttr.create("recompute", "r", MFnNumericData::kBoolean, 0, &status); addAttribute(recomputeAttr); addAttribute(outTriangleCoordinates); attributeAffects(recomputeAttr, outTriangleIndex); attributeAffects(recomputeAttr, outTriangleBarycentric); attributeAffects(recomputeAttr, outTriangleCoordinates);

I am not even sure, if I should have to add dependencies both on the arrays and the compound attribute.

 

When I set a breakpoint on the compute function, it gets hit when I open the node editor or show the attribute in the attribute editor by removing the setHidden line. But when the other node is connected, compute is not called and so the other node is not computed as well.

 

Is this a problem with connecting compound attributes and not their elements?

0 Likes
Reply
Accepted solutions (1)
3,623 Views
15 Replies
Replies (15)

cheng_xi_li
Autodesk Support
Autodesk Support

Hi,

 

Have you implemented MPxNode::setDependentsDirty? According to the document, 

 

For cases where multi compound attributes are dirtied, it is the programmer's responsibility to define ALL affects relationships. Dirtying the parent plug of a multi does not imply that all of its children will be marked dirty. Likewise, dirtying a child attribute does not imply the parent of the multi is dirty. This must be explicitly defined using the affected plug array. The following example demonstrates how one would dirty both the element affected and the parent plug.

 

Yours,

Li

Anonymous
Not applicable

No, I only tried all possible combinations of setting affects for compound and the arrays. I will look into the setDependentsDirty function.

0 Likes

Anonymous
Not applicable

setDependentsDirty is called, but neither the compute function of the own Node nor the compute function of the connected node are called.

 

I am currently using it like this and kept the attributeAffects in initialize (as I suspected the own compute function to need them):

 

 

MStatus fieldTracerNode::setDependentsDirty(const MPlug &plugBeingDirtied, MPlugArray &affectedPlugs) {
	if(plugBeingDirtied.attribute() == inMeshAttr ||
	   // ...
	   plugBeingDirtied.attribute() == recomputeAttr) {
		affectedPlugs.append(MPlug(thisMObject(), outTriangleBarycentric));
		affectedPlugs.append(MPlug(thisMObject(), outTriangleIndex));
		affectedPlugs.append(MPlug(thisMObject(), outTriangleCoordinates));
	}
	return MStatus();
}

A breakpoint on the .append calls is hit, but no breakpoint at the compute functions.

 

0 Likes

cheng_xi_li
Autodesk Support
Autodesk Support

Hi,

 

The elements of the array should be dirtied explicitly. Can you try to dirty each element of the array like sample?

 

        affectedPlugs.append(outArrayPlug);
        // Also visit each element.
        //
        unsigned int i,n = outArrayPlug.numElements();
        for (i = 0; i < n; i++) {
            MPlug elemPlug = outArrayPlug.elementByPhysicalIndex(i);
            affectedPlugs.append(elemPlug);
        }

 

Yours,

Li

Anonymous
Not applicable

For an initial computation I have outArrayPlug.numElements() == 0 as compute was not called, so the loop I added does not help.

 

When I understand it correctly, the shape should trigger the computation of node2 and node2 should trigger node1.compute via the compound attribute. And to pass a message down to the shape, I dirty the inPlug "recompute", which then should set the compound plug dirty in some way.

Should I dirty a not existing array element (elementByLogicalIndex(0)) to trigger the computations?

0 Likes

cheng_xi_li
Autodesk Support
Autodesk Support

Hi, 

 

Can you send a sample code and scene with steps to reproduce to us? I'll look at it next Monday.

 

Yours,

Li

Anonymous
Not applicable

I'll try to extract a minimum example. Who knows, maybe I find the problem while doing so ;-).

0 Likes

Anonymous
Not applicable

Hello.

Here a header only node implementation of two minimal nodes for this. As I wrote this from scratch (as my actual code has many dependencies), there may be some other mistakes in there but I hope it is a good basis to discuss the problem with the node dependencies.

#pragma once

#include <maya/MPxNode.h>
#include <maya/MFnCompoundAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MArrayDataBuilder.h>
#include <maya/MPlugArray.h>
#include <maya/MFnMesh.h>
#include <maya/MPointArray.h>

MObject nodeACompoundAttr, nodeAIntArrayAttr, nodeARecomputeAttr;
MObject nodeBCompoundAttr, nodeBIntArrayAttr, nodeBMeshAttr;

class nodeA : public MPxNode {
public:
MStatus compute(const MPlug& plug, MDataBlock& data) {
if(!data.inputValue(nodeARecomputeAttr).asBool()) {
return MStatus::kSuccess;
}
MArrayDataHandle adh = data.outputArrayValue(nodeAIntArrayAttr);
MArrayDataBuilder adb = adh.builder();
unsigned int size = 10; // size of my array
while(adb.elementCount() > size) { adb.removeElement(adb.elementCount()-1); }
while(adb.elementCount() < size) { adb.addLast(); }
adh.set(adb);
for(unsigned int i = 0; i < size; i++) {
adh.jumpToArrayElement(i);
adh.outputValue().setInt(i);
}
return MStatus::kSuccess;
};
MStatus setDependentsDirty(const MPlug& plugBeingDirtied, MPlugArray &affectedPlugs) {
if(plugBeingDirtied.attribute() == nodeARecomputeAttr) {
affectedPlugs.append(MPlug(thisMObject(), nodeACompoundAttr));
affectedPlugs.append(MPlug(thisMObject(), nodeAIntArrayAttr));
MPlug arrayPlug(thisMObject(), nodeAIntArrayAttr);
for(unsigned int i = 0; i < arrayPlug.numElements(); i++) {
affectedPlugs.append(arrayPlug.elementByLogicalIndex(i));
}
}
return MStatus::kSuccess;
};
static void* creator() { return new nodeA(); };
static MStatus initialize() {
MFnCompoundAttribute cAttr;
MFnNumericAttribute nAttr;
nodeACompoundAttr = cAttr.create("compound", "c");
nodeAIntArrayAttr = nAttr.create("intarray", "ia", MFnNumericData::kInt, 0);
nAttr.setArray(true);
nAttr.setUsesArrayDataBuilder(true);
cAttr.addChild(nodeAIntArrayAttr);
cAttr.setHidden(true);
nodeARecomputeAttr = nAttr.create("recompute", "r", MFnNumericData::kBoolean, 0);

addAttribute(nodeACompoundAttr);
addAttribute(nodeARecomputeAttr);
attributeAffects(nodeARecomputeAttr, nodeACompoundAttr);
attributeAffects(nodeARecomputeAttr, nodeAIntArrayAttr);
return MStatus::kSuccess;
};
static MTypeId id;
};

class nodeB : public MPxNode {
public:
MStatus compute(const MPlug& plug, MDataBlock& data) {
MArrayDataHandle adh = data.inputArrayValue(nodeBIntArrayAttr);
adh.jumpToElement(0);
for(unsigned int i = 0; i < adh.elementCount(); i++) {
// calculate something
adh.jumpToElement(i);
}
MFnMesh meshFn;
MPointArray pointArray;
MIntArray polygonCounts;
MIntArray polygonConnects;
MObject mesh = meshFn.create(0, 0, pointArray, polygonCounts, polygonConnects);
data.outputValue(nodeBMeshAttr).set(mesh);
return MStatus::kSuccess;
};

static void* creator() { return new nodeB(); };
static MStatus initialize() {
MFnCompoundAttribute cAttr;
MFnNumericAttribute nAttr;
MFnTypedAttribute tAttr;
nodeBCompoundAttr = cAttr.create("compound", "c");
nodeBIntArrayAttr = nAttr.create("intarray", "ia", MFnNumericData::kInt, 0);
nAttr.setArray(true);
nAttr.setUsesArrayDataBuilder(true);
cAttr.addChild(nodeBIntArrayAttr);
cAttr.setHidden(true);
nodeBMeshAttr = tAttr.create("mesh", "m", MFnData::kMesh);

addAttribute(nodeBCompoundAttr);
addAttribute(nodeBMeshAttr);
attributeAffects(nodeBCompoundAttr, nodeBMeshAttr);
return MStatus::kSuccess;
};
static MTypeId id;
};

MTypeId nodeA::id(0x80028);
MTypeId nodeB::id(0x80029);

/*
run MEL:

createNode nodeA;
createNode nodeB;
connectAttr nodeA1.compound nodeB1.compound;
createNode mesh;
connectAttr nodeB1.mesh polySurfaceShape1.inMesh;

*/

nodeA.compute is only hit, when I hover the plug "compound" of nodeA or nodeB in the node editor, but not on insertion or when changing the recompute checkbox.

setDependentsDirty is always hit when changing the checkbox.

nodeB.compute is hit one time only, probably when the compoundAttr is still dirty after creation.

0 Likes

cheng_xi_li
Autodesk Support
Autodesk Support
Accepted solution

Hi,

 

There are several error in the sample,

 

1.Accessing compounds

 

Please get compound first then accessing to it's children with child command like below

 

 

		MDataHandle compound = data.outputValue(nodeACompoundAttr);
		MArrayDataHandle adh = data.outputArrayValue(nodeAIntArrayAttr);
		MArrayDataBuilder adb = adh.builder();
		unsigned int size = 10; // size of my array
		while (adb.elementCount() > size) { adb.removeElement(adb.elementCount() - 1); }
		while (adb.elementCount() < size) { adb.addLast(); }
		adh.set(adb);
		for (unsigned int i = 0; i < size; i++) {
			adh.jumpToArrayElement(i);
			adh.outputValue().setInt(i);
		}

 

2. Not cleaning the output plug after compute

 

Please clean the dirtied plug after compute, the dirty will be broadcast one once if you don't clean it.

 

After you've posted code, it came to my mind that setDependentsDirty might not be required after EM was introduced to Maya, so you should be able to remove them right now.

 

The fixed code is attached below

 

 

#include <maya/MPxNode.h>
#include <maya/MFnCompoundAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MArrayDataBuilder.h>
#include <maya/MPlugArray.h>
#include <maya/MFnMesh.h>
#include <maya/MPointArray.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>

MObject nodeACompoundAttr, nodeAIntArrayAttr, nodeARecomputeAttr;
MObject nodeBCompoundAttr, nodeBIntArrayAttr, nodeBMeshAttr;

class nodeA : public MPxNode {
public:
	MStatus compute(const MPlug& plug, MDataBlock& data) {

		MGlobal::displayInfo(plug.name());
		MDataHandle compound = data.outputValue(nodeACompoundAttr);
		MArrayDataHandle adh = data.outputArrayValue(nodeAIntArrayAttr);
		MArrayDataBuilder adb = adh.builder();
		unsigned int size = 10; // size of my array
		while (adb.elementCount() > size) { adb.removeElement(adb.elementCount() - 1); }
		while (adb.elementCount() < size) { adb.addLast(); }
		adh.set(adb);
		for (unsigned int i = 0; i < size; i++) {
			adh.jumpToArrayElement(i);
			adh.outputValue().setInt(i);
		}
		adh.setAllClean();
		compound.setClean();

	
		return MStatus::kSuccess;
	};


	static void* creator() { return new nodeA(); };
	static MStatus initialize() {
		MFnCompoundAttribute cAttr;
		MFnNumericAttribute nAttr;
		nodeACompoundAttr = cAttr.create("compound", "c");
		nodeAIntArrayAttr = nAttr.create("intarray", "ia", MFnNumericData::kInt, 0);
		nAttr.setArray(true);
		nAttr.setUsesArrayDataBuilder(true);
		cAttr.addChild(nodeAIntArrayAttr);
		cAttr.setHidden(true);
		cAttr.setReadable(true);
		cAttr.setWritable(true);
		cAttr.setStorable(true);
		nodeARecomputeAttr = nAttr.create("recompute", "r", MFnNumericData::kBoolean, 0);

		addAttribute(nodeACompoundAttr);
		addAttribute(nodeARecomputeAttr);
		
		attributeAffects(nodeARecomputeAttr, nodeACompoundAttr);
		attributeAffects(nodeARecomputeAttr, nodeBIntArrayAttr);

		return MStatus::kSuccess;
	};
	static MTypeId id;
};

class nodeB : public MPxNode {
public:
	MStatus compute(const MPlug& plug, MDataBlock& data) {
		
	
		MDataHandle compoundValue = data.inputValue(nodeBCompoundAttr);	
		MArrayDataHandle adh = compoundValue.child(nodeBIntArrayAttr);
		adh.jumpToElement(0);
		for (unsigned int i = 0; i < adh.elementCount(); i++){		
			adh.jumpToElement(i);
		}
	
		auto handle = data.outputValue(nodeBMeshAttr);
	
		MFnMesh meshFn;
		MPointArray pointArray;
		MIntArray polygonCounts;
		MIntArray polygonConnects;
		MObject mesh = meshFn.create(0, 0, pointArray, polygonCounts, polygonConnects);
		handle.set(mesh);
		handle.setClean();
		return MStatus::kSuccess;
	};
		
	static void* creator() { return new nodeB(); };
	static MStatus initialize() {
		MFnCompoundAttribute cAttr;
		MFnNumericAttribute nAttr;
		MFnTypedAttribute tAttr;
		nodeBCompoundAttr = cAttr.create("compound", "c");
		nodeBIntArrayAttr = nAttr.create("intarray", "ia", MFnNumericData::kInt, 0);
		nAttr.setArray(true);
		nAttr.setUsesArrayDataBuilder(true);
		cAttr.addChild(nodeBIntArrayAttr);
		nodeBMeshAttr = tAttr.create("mesh", "m", MFnData::kMesh);

		addAttribute(nodeBCompoundAttr);
		addAttribute(nodeBMeshAttr);
		attributeAffects(nodeBCompoundAttr, nodeBMeshAttr);
		return MStatus::kSuccess;
	};
	static MTypeId id; 
};

MTypeId nodeA::id(0x80028);
MTypeId nodeB::id(0x80029);

MStatus initializePlugin(MObject obj)
{
	MStatus   status;
	MFnPlugin plugin(obj, PLUGIN_COMPANY, "6.0", "Any");

	status = plugin.registerNode("nodeA", nodeA::id, nodeA::creator,
		nodeA::initialize);
	status = plugin.registerNode("nodeB", nodeB::id, nodeB::creator,
		nodeB::initialize);
	if (!status) {
		status.perror("registerNode");
		return(status);
	}

	return(status);
}

MStatus uninitializePlugin(MObject obj)
{
	MStatus   status;
	MFnPlugin plugin(obj);

	status = plugin.deregisterNode(nodeA::id);
	status = plugin.deregisterNode(nodeB::id);

	if (!status) {
		status.perror("deregisterNode");
		return(status);
	}

	return(status);
}

 

Yours,

Li

 

cheng_xi_li
Autodesk Support
Autodesk Support

P.S.

 

The setStorable/Readonly/Writable is not required, you can remove them.

 

Yours,

Li

Anonymous
Not applicable

Thank you I'll try it that way. I think I probably can cleanup more, like the loops to reduce/increase the number of children, but I first need to be sure the current version is working.

I find the up/down propagation still a bit confusing, like if a input plug is dirty (and triggers output recomputation) or an output plug is dirty and needs to be recomputed from (all) input and if outputValue().set() cleans the plugs automatically. Can I use inputValue().isDirty to check for what has changed since last recomputation or is dirty/clean just for the outputs in the current node evaluation?

With kind regards,
Alex

0 Likes

Anonymous
Not applicable

Works perfectly. Thank you!

0 Likes

Anonymous
Not applicable
MDataHandle compoundValue = data.inputValue(nodeBCompoundAttr);	
MArrayDataHandle adh = compoundValue.child(nodeBIntArrayAttr);

As far as I see, this casts MDataHandle from child() to MArrayDataHandle. Is this the correct way to do it?

0 Likes

cheng_xi_li
Autodesk Support
Autodesk Support
Dirty is a status indicates that Maya should compute an attribute when it is required. When you have finished calculations, you'll need to tell Maya that my company pute is finished and reset its status to clean. If it remains dirty, it means dirty has been already dispatched and don't have to do that again. I don't think you don't have to check if it is dirty. When you are getting a value during the compute, Maya will check and resolve it automatically. In your original code, the way to get value is incorrect so Maya never does a compute and there is no clean in the compute, so the dirty will only broadcast once. In your original code(without clean), if you keep executing getAttr nodeB.mesh after ticking nodeA.recompute, you'll find Maya always calls nodeB::complete because the mesh is dirty. But because dirty will only pass once, keep modifying nodeA.compute won't affect it at all.

Yours,
Li

cheng_xi_li
Autodesk Support
Autodesk Support
It shouldn't be a problem.

Yours,
Li