The following example demonstrates a simple custom dependency graph node that derives from the base MPxNode parent class. It takes a single floating-point number as input, computes the sine of the number, and outputs the result.
#include <string.h> #include <iostream.h> #include <math.h> #include <maya/MString.h> #include <maya/MFnPlugin.h>
This is still a plug-in so you still need MFnPlugin.h. However, you use a different method to register a node than a command.
#include <maya/MPxNode.h> #include <maya/MTypeId.h> #include <maya/MPlug.h> #include <maya/MDataBlock.h> #include <maya/MDataHandle.h>
These header files are used by most plug-in dependency graph nodes.
#include <maya/MFnNumericAttribute.h>
There are a number of different types of attributes (which you will be introduced to) and the ones you need are dependent on the type of node you write. For this example, only numeric data is used.
class sine : public MPxNode {
User-defined dependency graph nodes are derived from the MPxNode class.
public: sine();
The constructor for the node is called whenever an instance of this node is created. This can either be when the createNode command is called, the MFnDependencyNode::create() method is invoked, etc.
virtual ~sine();
The destructor is only called when the node is truly deleted. Because of the undo queue in Maya, deleting the node does not actually cause the node’s destructor to be called, so if the deletion is undone, the node can be returned without recreating it. Generally, a deleted node’s destructor is only called when the undo queue is flushed.
virtual MStatus compute( const MPlug& plug, MDataBlock& data );
The compute() method is the brains of a node. It does the actual work of the node using the inputs on the node to generate its outputs.
static void* creator();
The creator() method serves the same purpose as the creator method on commands. It allows Maya to instantiate instances of this node. It is called every time a new instance of the node is requested by either the createNode command or the MFnDependencyNode::create() method.
static MStatus initialize();
The initialize() method is called by the registration mechanism for dependency nodes. As a result it is called once immediately after a plug-in containing a user-defined node is loaded. It is used to define the inputs and outputs of the node (for instance, its attributes).
These two MObjects are the attributes of the sine node. You are free to use any names for a node’s attributes—input and output are just used here for clarity.
static MTypeId id;
Each node requires a unique identifier which is used by MFnDependencyNode::create() to identify which node to create, and by the Maya file format.
For local testing of nodes you can use any identifier between 0x00000000 and 0x0007ffff, but for any node that you plan to use for more permanent purposes, you should get a universally unique id from http://mayaid.autodesk.io/. You will be assigned a unique range that you can manage on your own.
}; MTypeId sine::id( 0x80000 );
This initializes the node’s identifier to a unique tag. These tags must be unique between all nodes (the tag is used by the file format to recreate the node) and will be assigned to API users by Autodesk.
The attributes of the node are initialized to NULL values.
void* sine::creator() { return new sine; }
As mentioned earlier, the creator() method simply returns new instances of this node. In more complex situations where several nodes may need to be interconnected, it is possible to define a single creator for the connected nodes and have this creator allocate and connect all the nodes together.
MStatus sine::initialize() {
The initialize method is called only once when the node is first registered with Maya. In this method you define the attributes of the node, what data comes in and goes out of the node that other nodes may want to connect to.
MFnNumericAttribute nAttr;
This example only uses numeric data so all it’s attributes are numeric and therefore only MFnNumericAttribute is necessary.
output = nAttr.create( "output", "out", MFnNumericData::kFloat, 0.0 ); nAttr.setWritable(false); nAttr.setStorable(false);
The first of these three lines defines the output attribute for the sine node. When defining an attribute, you must specify a long name (four characters or longer) and a short name (no more than three characters) for the attribute. These names are used in MEL scripts and UI editors to identify particular attributes. While it is not necessary, it is generally a good idea if the long name is the same as the C++ identifier for the attribute—in this example they are both called "output".
The create method also indicates the type of the attribute, in this case a float (MFnNumericData::kFloat) and sets its default value to zero. The names of attributes need only be unique within a node, different nodes may have similarly named attributes.
The next two lines set specific characteristics of this attribute. Since this is the output of the sine node, it is not writable by other nodes. That means it is not possible for the output attribute of another node to be connected to this attribute. Also, because this is an output, it is not necessary to store the output when writing a file, since the output can be generated from the inputs. (It wouldn’t cause problems if you stored an output—it would just waste space.)
When instantiating a new node, Maya sets the following characteristics to true:
and the following characteristics to false:
input = nAttr.create( "input", "in", MFnNumericData::kFloat, 0.0 ); nAttr.setStorable(true);
The initialization of the input attribute is similar to the output attribute, but since the value of the input cannot be calculated by the node, it must be stored when the node is stored.
addAttribute( input ); attributeAffects( input, output );
The input attribute is added as an attribute to the sine node. The attributeAffects() method is used to indicate when the input attribute affects the output attribute. This knowledge allows Maya to optimize dependencies in the graph in more complex nodes where there may be several inputs and outputs, but not all the inputs affect all the outputs.
addAttribute( output );
The output attribute is added to the sine node. Since the output attribute is generated it does not affect the input attribute so no attributeAffects() method is required.
return MS::kSuccess;
Success is returned to indicate to Maya that the node was successfully initialized. A failure return would stop the initialization, and since the initialization method is called only once, the node would never be usable in the session. Failure returns should be made whenever a resource is unavailable that the node requires.
} sine::sine() {} sine::~sine() {}
Since this is a very simple node, the constructor and destructor do not do anything.
MStatus sine::compute( const MPlug& plug, MDataBlock& data ) {
The compute() method is the brains of a dependency graph node doing all the actual work of the node. It takes two arguments. The first is a reference to the plug for which a compute is being requested, and the second is the data block for the node. Plugs and data blocks will be described in more detail in a later section.
MStatus returnStatus; if( plug == output ) {
A plug can be thought of as the recomputed attribute. This test checks which output attribute the recompute is being requested for.
In this simple example, it will only be the output attribute, but in more complex nodes it could be any of the outputs. You should always test that the plug represents a known attribute. For example, someone may attach a dynamic attribute to your node that might have connections. This could cause your compute method to be called when none of your inputs have changed.
MDataHandle inputData = data.inputValue( input, &returnStatus );
The data block contains all the data for this instance of the node. For efficiency this data is kept as a single block, so the data handles are used to reference a piece of the block. In this case the data handle is being set to reference the input attribute.
if( returnStatus != MS::kSuccess ) cerr << "ERROR getting data" << endl; else { float result = sin( inputData.asFloat() );
The data is retrieved from the data handle through the as*() methods on MFnDataHandle. It is vital that the as*() method matches the declared type of the attribute. The input attribute was declared as MFnNumericAttribute::kFloat so the data must be retrieved with asFloat(). Mixing types and retrieve methods will cause fatal errors. For example declaring an attribute as MFnNumericAttribute::kDouble and retrieving it with asFloat() will result in a fatal error.
MDataHandle outputHandle = data.outputValue( output );
A new data handle is allocated to be used to reference the piece of the data block for the output attribute.
outputHandle.set( result );
The output attribute is then assigned the result of the calculation.
data.setClean(plug);
The plug in the data block which caused the recompute is marked clean indicating that it has been newly recomputed.
} } return MS::kSuccess; }
A successful status return indicates that the computation completed properly.
MStatus initializePlugin( MObject obj ) { MStatus status; MFnPlugin plugin( obj, "My plug-in", "1.0", "Any"); status = plugin.registerNode( "sine", sine::id, sine::creator, sine::initialize );
The plug-in requires an initializePlugin() and an uninitializePlugin() as with command plug-ins, but rather than using registerCommand(), registerNode() is used to add the node to Maya’s database of nodes. The initialize method for the node is called as a result of the call to registerNode. If the initialize method returns a failure code, registerNode will also fail and your node will fail to load.
return status; } MStatus uninitializePlugin( MObject obj) { MStatus status; MFnPlugin plugin( obj ); status = plugin.deregisterNode( sine::id ); return status; }
And that’s a simple dependency graph node.