MPxData in Maya Shading Node

Hey folks

I’ve been lurking for a little over a year now, soaking up the knowledge of all the smart folks here, but I finally came across something I couldn’t fix on my own. Hopefully someone here can help.

As part of a larger system, I’m writing Maya DG shading nodes, and I want to be able to pass them a custom data type that holds an array of matrices. It actually does work in the viewport/rendering, but when I try to do an IPR render (or sometimes when it tries to create swatches for the Hypergraph), it has issues. It will initially work, but if I try to change the value of attributes Maya throws a fatal error at me and crashes.

Via the Visual Studio debugger, I have determined that the crash itself doesn’t happen in my code, but tends to jump around. Also, Visual Studio will sometimes report that the heap has been corrupted, so I suppose it may be running out of memory or somewhere I’m overwriting something? Some searching on the topic seems to suggest that Maya doesn’t delete MPxData objects after it creates them, and since a shading node is computed many times during rendering maybe it is making too many too fast.

I’ve stripped the plugin of most of the actual computation. It tries to access the plugin data from within a shading node, but doesn’t use it and just sets the out color to an input color. I’ve indicated in the compute method which lines stop it from crashing when removed (namely, the ones that try to access the data from the handle). There is an unused attribute on the node that requests the world space position, and removing that causes the crash to go away in this example, but I do use that attribute in the real node.

Any help is much appreciated, as I’ve been attempting to debug this for the past 4-5 hours with no luck.

Code for 3 nodes follows. The SingleProjector makes some data and outputs it to the DotProjNode, which just connects to the color on any material. The ProjectorData is the MPxData in question.

Here is the shading node header:

//header file for dot projector shading node

#ifndef DOTPROJ_H
#define DOTPROJ_H

#include <maya/MPxNode.h>
#include <maya/MString.h>
#include <maya/MFloatVector.h>
#include <maya/MFloatMatrix.h>

class DotProjNode : public MPxNode
{
	private:
		//Shading attributes
		static MObject wldPoint; //world space location

		//Input attributes
		static MObject backColor;
		static MObject projectorData;

		//Output attributes
		static MObject outColor;

	public:
		DotProjNode();
		virtual ~DotProjNode();

		virtual MStatus compute( const MPlug&, MDataBlock& );
		virtual void postConstructor();

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

		//static attributes for node registration
		static const MTypeId id;
		static const MString nodeName;
		static const MPxNode::Type nodeType;
};

#endif

The cpp

//Source for Dot Projection Node

#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MPxData.h>
#include <maya/MFnPluginData.h>

#include "dotProj.h"
#include "dingCommon.h" //just the MAKEINPUT and MAKEOUTPUT macros
#include "projectorData.h"


//static node registration data
const MTypeId DotProjNode::id( 0x41 );
const MString DotProjNode::nodeName("dDotProjNode");
const MPxNode::Type DotProjNode::nodeType(MPxNode::kDependNode);

//Shading attributes
MObject DotProjNode::wldPoint; //world space location

//Input attributes
MObject DotProjNode::backColor;
MObject DotProjNode::projectorData;


//Output attributes
MObject DotProjNode::outColor;


DotProjNode::DotProjNode()
{
}

DotProjNode::~DotProjNode()
{
}

void DotProjNode::postConstructor()
{
	setMPSafe(true);
	setExistWithoutInConnections(true);
	setExistWithoutOutConnections(true);
}

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

//perform the actual computation
MStatus DotProjNode::compute(const MPlug& plug, MDataBlock& block) 
{
	MStatus stat;

	//make sure the requested output is either outColor or one of it's channels
	if((plug != outColor) && (plug.parent() != outColor))
		return MS::kUnknownParameter;

	//grab the input values
	MFloatVector &backColor = block.inputValue(this->backColor).asFloatVector(); //background color

	//get projector data
	MDataHandle dataHandle = block.inputValue(projectorData, &stat);
	CHECK_MSTATUS(stat);
	MPxData *pluginData = dataHandle.asPluginData();              //<-----------the problem happens when this line is there
	ProjectorData *data = static_cast<ProjectorData*>(pluginData);

	//MFnPluginData pluginData(dataHandle.data());                   <----------causes the same problem

	//set ouput color attribute
	MDataHandle outColorHandle = block.outputValue( outColor );
	outColorHandle.setMFloatVector(backColor);
	outColorHandle.setClean();

	return MS::kSuccess;
}

//initialize and add all attributes for this node
MStatus DotProjNode::initialize()
{
	MFnNumericAttribute nAttr;
	MFnTypedAttribute tAttr;

	//create output attributes
	outColor = nAttr.createColor("outColor", "oc");
	MAKE_OUTPUT(nAttr);
	CHECK_MSTATUS (addAttribute(outColor));

	//create the input attributes
	wldPoint = nAttr.createPoint("pointWorld", "pw");         //<-------- doesn't crash if this isn't there
	CHECK_MSTATUS (nAttr.setStorable(false));                 //even though this attribute is never actually used in this simple example
	CHECK_MSTATUS (nAttr.setHidden(true));
	CHECK_MSTATUS (addAttribute(wldPoint));
	CHECK_MSTATUS (attributeAffects (wldPoint, outColor));

	backColor = nAttr.createColor("backColor", "bc");
	MAKE_INPUT(nAttr);
	CHECK_MSTATUS (addAttribute(backColor));
	CHECK_MSTATUS (attributeAffects (backColor, outColor));

	projectorData = tAttr.create("projectorData", "pd", ProjectorData::id);
	CHECK_MSTATUS (tAttr.setStorable(false));
	CHECK_MSTATUS (tAttr.setConnectable(true));
	CHECK_MSTATUS (addAttribute(projectorData));
	CHECK_MSTATUS (attributeAffects (projectorData, outColor));

	return MS::kSuccess;
}

Then here are the other files that go with it:
The data header

#ifndef DING_PROJECTOR_DATA_H
#define DING_PROJECTOR_DATA_H

#include <maya/MPxData.h>
#include <maya/MTypeId.h>
#include <maya/MString.h>
#include <maya/MFloatMatrix.h>

class ProjectorData : public MPxData
{
	private:
		bool indexOk(const unsigned int index) const;
		unsigned int length;
		MFloatMatrix *matrixArray;

	public:
		ProjectorData();
		ProjectorData(const unsigned int length);
		~ProjectorData();

		//functions required to be overridden
		virtual void copy(const MPxData &other);
		MTypeId typeId() const;
		MString name() const;

		//itterator functions
		const MFloatMatrix &operator[](const unsigned int index) const;
		MFloatMatrix &operator[](const unsigned int index);
		unsigned int getLength() const {return length;};
		void setLength(const unsigned int newLength);

		//statics
		static const MTypeId id;
		static const MString typeName;
		static void* creator();
		static MFloatMatrix identity;
};

#endif

The data source

#include <maya/MIOStream.h>

#include "projectorData.h"
#include "dingDefines.h" //for nullptr on older compilers

//fill in the statics
const MTypeId ProjectorData::id(0x43);
const MString ProjectorData::typeName("dProjectorData");
MFloatMatrix ProjectorData::identity = MFloatMatrix();

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

ProjectorData::ProjectorData():
	length(0),
	matrixArray(nullptr)
{
}

ProjectorData::ProjectorData(const unsigned int inLength):
	length(0),
	matrixArray(nullptr)
{
	setLength(inLength);
}

ProjectorData::~ProjectorData()
{
	delete [] matrixArray;
	length = 0;
}

//set the array length to the given value and copy over the old data
//if the new value is the same as the old value, there is no change
//if it is smaller than the old value, data will be lost
void ProjectorData::setLength(const unsigned int newLength)
{
	//if the new value is the same as the old value, do nothing
	if(newLength != length)
	{
		//make a new array
		MFloatMatrix *temp = new MFloatMatrix[newLength];

		//loop thorough and copy the old data, if any exists
		if(matrixArray)
		{
			//make sure we don't try to copy more than exist
			unsigned int max = newLength;
			if(newLength > length) max = length;
			for(unsigned int i = 0; i < max; i++)
			{
				temp[i] = matrixArray[i];
			}
		}

		//delete the old matrix and set the pointer to the new one
		delete [] matrixArray;
		matrixArray = temp;
		length = newLength;
	}
}

//check that the given index is within our length
inline bool ProjectorData::indexOk(const unsigned int index) const
{
	return (index < length);
}

//get the matrix at index (const version)
const MFloatMatrix& ProjectorData::operator[](const unsigned int index) const
{
	if(indexOk(index))
	{
		return matrixArray[index];
	}
	else
	{
		cerr << "const ProjectorData::operator[] - Index error, returning" <<
			" identity matrix" << endl;
		return identity;
	}
}

//get the matrix at index
MFloatMatrix& ProjectorData::operator[](const unsigned int index)
{
	if(indexOk(index))
	{
		return matrixArray[index];
	}
	else
	{
		cerr << "ProjectorData::operator[] - Index error, returning" <<
			" identity matrix" << endl;
		return identity;
	}
}

//copy the data from other into this
void ProjectorData::copy(const MPxData &other)
{
	if(other.typeId() == ProjectorData::id)
	{
		//it is one of us so copy over the data
		const ProjectorData* otherData = (const ProjectorData*)&other;

		setLength(otherData->length);

		for(unsigned int i = 0; i < length; i++)
		{
			matrixArray[i] = (*otherData)[i];
		}
	}
}

MTypeId ProjectorData::typeId() const
{
	return ProjectorData::id;
}

MString ProjectorData::name() const
{
	return ProjectorData::typeName;
}

Header for a node that generates some of the data and outputs it

#ifndef DING_SINGLE_PROJECTOR_H
#define DING_SINGLE_PROJECTOR_H

#include <maya/MPxNode.h>

class SingleProjector : public MPxNode
{
	private:
		//input attributes
		static MObject preRot;
		static MObject trans;
		static MObject postRot;

		//output attributes
		static MObject outProj;

	public:
		SingleProjector();
		virtual ~SingleProjector();

		virtual MStatus compute( const MPlug&, MDataBlock& );
		virtual void postConstructor();

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

		//static attributes for node registration
		static const MTypeId id;
		static const MString nodeName;
		static const MPxNode::Type nodeType;
};

#endif

And the source

#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MFloatMatrix.h>
#include <maya/MEulerRotation.h>
#include <maya/MVector.h>
#include <maya/MFnPluginData.h>

#include "singleProjector.h"
#include "dingMath.h" //for k::Radians conversion
#include "dingCommon.h" //just the MAKEINPUT and MAKEOUTPUT macros
#include "projectorData.h"


//static node registration data
const MTypeId SingleProjector::id( 0x44 );
const MString SingleProjector::nodeName("dSingleProjectorNode");
const MPxNode::Type SingleProjector::nodeType(MPxNode::kDependNode);

//Input attributes
MObject SingleProjector::preRot; //pre-translation rotation
MObject SingleProjector::trans; //translation
MObject SingleProjector::postRot; //post-translation rotation

//Output attributes
MObject SingleProjector::outProj; //output projection data


SingleProjector::SingleProjector()
{
}

SingleProjector::~SingleProjector()
{
}

void SingleProjector::postConstructor()
{
	setExistWithoutInConnections(true);
	setExistWithoutOutConnections(true);
}

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

//perform the actual computation
MStatus SingleProjector::compute(const MPlug& plug, MDataBlock& block) 
{
	MStatus status;

	//make sure the requested output is either outColor or one of it's channels
	if(plug != outProj)	return MS::kUnknownParameter;

	//grab the input values
	float3 &preRot = block.inputValue(this->preRot).asFloat3();
	float3 &trans = block.inputValue(this->trans).asFloat3();
	float3 &postRot = block.inputValue(this->postRot).asFloat3();

	//create the euler rotations
	MEulerRotation pre(preRot[0] * K::radians, preRot[1] * K::radians, preRot[2] * K::radians);
	MEulerRotation post(postRot[0] * K::radians, postRot[1] * K::radians, postRot[2] * K::radians);

	//create the transformation
	MTransformationMatrix mat;

	//apply the transformations
	mat.rotateBy(pre, MSpace::kPreTransform, &status);
	CHECK_MSTATUS(status);

	CHECK_MSTATUS(mat.addTranslation(MVector(trans[0], trans[1], trans[2]), MSpace::kTransform));

	mat.rotateBy(post, MSpace::kTransform, &status);
	CHECK_MSTATUS(status);

	//create the projector data and add it
	MFnPluginData dataCreator;
	dataCreator.create(ProjectorData::id, &status);
	CHECK_MSTATUS(status);
	ProjectorData *newData = static_cast<ProjectorData*>(dataCreator.data(&status));
	CHECK_MSTATUS(status);

	//set the size to 1 and add the matrix
	newData->setLength(1);
	(*newData)[0] = MFloatMatrix(mat.asMatrixInverse().matrix);

	//output it
	MDataHandle outHandle = block.outputValue(outProj, &status);
	CHECK_MSTATUS(status);
	outHandle.set(newData);

	return MS::kSuccess;
}

//initialize and add all attributes for this node
MStatus SingleProjector::initialize()
{
	MFnNumericAttribute nAttr;
	MFnTypedAttribute tAttr;

	//create output attributes
	outProj = tAttr.create("outProj", "op", ProjectorData::id);
	MAKE_OUTPUT(tAttr);
	CHECK_MSTATUS (addAttribute(outProj));

	//create the input attributes
	preRot = nAttr.createPoint("preRot", "pr");
	MAKE_INPUT(nAttr);
	CHECK_MSTATUS (addAttribute(preRot));
	CHECK_MSTATUS (attributeAffects (preRot, outProj));

	trans = nAttr.createPoint("translation", "t");
	MAKE_INPUT(nAttr);
	CHECK_MSTATUS (addAttribute(trans));
	CHECK_MSTATUS (attributeAffects (trans, outProj));

	postRot = nAttr.createPoint("postRot", "psr");
	MAKE_INPUT(nAttr);
	CHECK_MSTATUS (addAttribute(postRot));
	CHECK_MSTATUS (attributeAffects (postRot, outProj));

	return MS::kSuccess;
}

And for good measure, the registration code

//Main cDing plugin file. Registers all plugin entities with Maya

#define cDING_VERSION "0.1"

#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>


//cDing includes
#include "dotProj.h"
#include "projectorData.h"
#include "singleProjector.h"

//Registers all nodes with Maya
MStatus initializePlugin(MObject obj)
{
	MFnPlugin plugin(obj, "Dejobaan", cDING_VERSION, "Any");

	CHECK_MSTATUS(plugin.registerData(ProjectorData::typeName,
		ProjectorData::id, ProjectorData::creator));

	CHECK_MSTATUS(plugin.registerNode(DotProjNode::nodeName, DotProjNode::id, 
		DotProjNode::creator, DotProjNode::initialize,
		DotProjNode::nodeType));
	CHECK_MSTATUS(plugin.registerNode(SingleProjector::nodeName, SingleProjector::id, 
		SingleProjector::creator, SingleProjector::initialize,
		SingleProjector::nodeType));

	return MS::kSuccess;
}

//Unregisters all nodes from Maya
MStatus uninitializePlugin(MObject obj)
{
	MFnPlugin plugin(obj);
	CHECK_MSTATUS(plugin.deregisterNode(DotProjNode::id));
	CHECK_MSTATUS(plugin.deregisterNode(SingleProjector::id));
	CHECK_MSTATUS(plugin.deregisterData(ProjectorData::id));

	return MS::kSuccess;
}

Thanks!