In the following topic, we outline the basic code structure of a Dependency Graph plug-in.
The following Python code is a sample Dependency Graph plug-in. This sample defines a utility (utility/general) Hypershade node named myNodeName. It has one input attribute which accepts a float (decimal number), and one output attribute which outputs the inputted float. This node can be created using cmds.createNode( 'myNodeName' ) or cmds.shadingNode( 'myNodeName', asUtility=True ). If it is created with the later command, or manually via the Maya Hypershade user interface (filter by typing 'My Node Name' in the text box), an instance of this node will appear in the Window > Rendering Editors > Hypershade > 'Utilities' Tab.
# pySampleDGNode.py import sys import maya.api.OpenMaya as OpenMaya # ... additional imports here ... def maya_useNewAPI(): """ The presence of this function tells Maya that the plugin produces, and expects to be passed, objects created using the Maya Python API 2.0. """ pass # Plug-in information: kPluginNodeName = 'myNodeName' # The name of the node. kPluginNodeClassify = 'utility/general' # Where this node will be found in the Maya UI. kPluginNodeId = OpenMaya.MTypeId( 0x87EFE ) # A unique ID associated to this node type. # Default attribute values sampleDefaultValue = 1 ########################################################## # Plug-in ########################################################## class myNode(OpenMaya.MPxNode): # Static variables which will later be replaced by the node's attributes. sampleInAttribute = OpenMaya.MObject() sampleOutAttribute = OpenMaya.MObject() def __init__(self): ''' Constructor. ''' OpenMaya.MPxNode.__init__(self) def compute(self, pPlug, pDataBlock): ''' Node computation method. - pPlug: A connection point related to one of our node attributes (could be an input or an output) - pDataBlock: Contains the data on which we will base our computations. ''' if( pPlug == myNode.sampleOutAttribute ): # Obtain the data handles for each attribute sampleInDataHandle = pDataBlock.inputValue( myNode.sampleInAttribute ) sampleOutDataHandle = pDataBlock.outputValue( myNode.sampleOutAttribute ) # Extract the actual value associated to our sample input attribute (we have defined it as a float) sampleInValue = sampleInDataHandle.asFloat() # ... perform the desired computation ... # Set the output value. sampleOutDataHandle.setFloat( sampleInValue ) # As an example, we just set the output value to be equal to the input value. # Mark the output data handle as being clean; it need not be computed given its input. sampleOutDataHandle.setClean() else: return OpenMaya.kUnknownParameter ########################################################## # Plug-in initialization. ########################################################## def nodeCreator(): ''' Creates an instance of our node class and delivers it to Maya as a pointer. ''' return myNode() def nodeInitializer(): ''' Defines the input and output attributes as static variables in our plug-in class. ''' # The following MFnNumericAttribute function set will allow us to create our attributes. numericAttributeFn = OpenMaya.MFnNumericAttribute() #================================== # INPUT NODE ATTRIBUTE(S) #================================== global sampleDefaultValue myNode.sampleInAttribute = numericAttributeFn.create( 'myInputAttribute', 'i', OpenMaya.MFnNumericData.kFloat, sampleDefaultValue ) numericAttributeFn.writable = True numericAttributeFn.storable = True numericAttributeFn.hidden = False myNode.addAttribute( myNode.sampleInAttribute ) # Calls the MPxNode.addAttribute function. #================================== # OUTPUT NODE ATTRIBUTE(S) #================================== myNode.sampleOutAttribute = numericAttributeFn.create( 'myOutputAttribute', 'o', OpenMaya.MFnNumericData.kFloat ) numericAttributeFn.storable = False numericAttributeFn.writable = False numericAttributeFn.readable = True numericAttributeFn.hidden = False myNode.addAttribute( myNode.sampleOutAttribute ) #================================== # NODE ATTRIBUTE DEPENDENCIES #================================== # If sampleInAttribute changes, the sampleOutAttribute data must be recomputed. myNode.attributeAffects( myNode.sampleInAttribute, myNode.sampleOutAttribute ) def initializePlugin( mobject ): ''' Initialize the plug-in ''' mplugin = OpenMaya.MFnPlugin( mobject ) try: mplugin.registerNode( kPluginNodeName, kPluginNodeId, nodeCreator, nodeInitializer, OpenMaya.MPxNode.kDependNode, kPluginNodeClassify ) except: sys.stderr.write( 'Failed to register node: ' + kPluginNodeName ) raise def uninitializePlugin( mobject ): ''' Uninitializes the plug-in ''' mplugin = OpenMaya.MFnPlugin( mobject ) try: mplugin.deregisterNode( kPluginNodeId ) except: sys.stderr.write( 'Failed to deregister node: ' + kPluginNodeName ) raise ########################################################## # Sample usage. ########################################################## ''' # Copy the following lines and run them in Maya's Python Script Editor: import maya.cmds as cmds cmds.loadPlugin( 'pySampleDGNode.py' ) cmds.createNode( 'myNodeName' ) # ... '''
# sampleDGNode.py import sys import maya.OpenMayaMPx as OpenMayaMPx import maya.OpenMaya as OpenMaya # ... additional imports here ... # Plug-in information: kPluginNodeName = 'myNodeName' # The name of the node. kPluginNodeClassify = 'utility/general' # Where this node will be found in the Maya UI. kPluginNodeId = OpenMaya.MTypeId( 0x87EFE ) # A unique ID associated to this node type. # Default attribute values sampleDefaultValue = 1 ########################################################## # Plug-in ########################################################## class myNode(OpenMayaMPx.MPxNode): # Static variables which will later be replaced by the node's attributes. sampleInAttribute = OpenMaya.MObject() sampleOutAttribute = OpenMaya.MObject() def __init__(self): ''' Constructor. ''' OpenMayaMPx.MPxNode.__init__(self) def compute(self, pPlug, pDataBlock): ''' Node computation method. - pPlug: A connection point related to one of our node attributes (could be an input or an output) - pDataBlock: Contains the data on which we will base our computations. ''' if( pPlug == myNode.sampleOutAttribute ): # Obtain the data handles for each attribute sampleInDataHandle = pDataBlock.inputValue( myNode.sampleInAttribute ) sampleOutDataHandle = pDataBlock.outputValue( myNode.sampleOutAttribute ) # Extract the actual value associated to our sample input attribute (we have defined it as a float) sampleInValue = sampleInDataHandle.asFloat() # ... perform the desired computation ... # Set the output value. sampleOutDataHandle.setFloat( sampleInValue ) # As an example, we just set the output value to be equal to the input value. # Mark the output data handle as being clean; it need not be computed given its input. sampleOutDataHandle.setClean() else: return OpenMaya.kUnknownParameter ########################################################## # Plug-in initialization. ########################################################## def nodeCreator(): ''' Creates an instance of our node class and delivers it to Maya as a pointer. ''' return OpenMayaMPx.asMPxPtr( myNode() ) def nodeInitializer(): ''' Defines the input and output attributes as static variables in our plug-in class. ''' # The following MFnNumericAttribute function set will allow us to create our attributes. numericAttributeFn = OpenMaya.MFnNumericAttribute() #================================== # INPUT NODE ATTRIBUTE(S) #================================== global sampleDefaultValue myNode.sampleInAttribute = numericAttributeFn.create( 'myInputAttribute', 'i', OpenMaya.MFnNumericData.kFloat, sampleDefaultValue ) numericAttributeFn.setWritable( True ) numericAttributeFn.setStorable( True ) numericAttributeFn.setHidden( False ) myNode.addAttribute( myNode.sampleInAttribute ) # Calls the MPxNode.addAttribute function. #================================== # OUTPUT NODE ATTRIBUTE(S) #================================== myNode.sampleOutAttribute = numericAttributeFn.create( 'myOutputAttribute', 'o', OpenMaya.MFnNumericData.kFloat ) numericAttributeFn.setStorable( False ) numericAttributeFn.setWritable( False ) numericAttributeFn.setReadable( True ) numericAttributeFn.setHidden( False ) myNode.addAttribute( myNode.sampleOutAttribute ) #================================== # NODE ATTRIBUTE DEPENDENCIES #================================== # If sampleInAttribute changes, the sampleOutAttribute data must be recomputed. myNode.attributeAffects( myNode.sampleInAttribute, myNode.sampleOutAttribute ) def initializePlugin( mobject ): ''' Initialize the plug-in ''' mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.registerNode( kPluginNodeName, kPluginNodeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDependNode, kPluginNodeClassify ) except: sys.stderr.write( 'Failed to register node: ' + kPluginNodeName ) raise def uninitializePlugin( mobject ): ''' Uninitializes the plug-in ''' mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.deregisterNode( kPluginNodeId ) except: sys.stderr.write( 'Failed to deregister node: ' + kPluginNodeName ) raise ########################################################## # Sample usage. ########################################################## ''' # Copy the following lines and run them in Maya's Python Script Editor: import maya.cmds as cmds cmds.loadPlugin( 'sampleDGNode.py' ) cmds.createNode( 'myNodeName' ) # ... '''
Dependency Graph plug-ins require the same entry and exit point functions as Command Plug-ins, namely: initializePlugin() and uninitializePlugin(). These two functions are respectively invoked when Maya loads and unloads the plug-in. Your plug-in will fail to load if these two functions are absent in your file. Dependency Graph plug-ins are used to create new node types by inheriting from MPxNode or one of its many subclasses. In the sample contained within this topic, we are inheriting directly from MPxNode, so we must register our node via MFnPlugin.registerNode().
def initializePlugin( mobject ): ''' Initialize the plug-in ''' mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.registerNode( kPluginNodeName, kPluginNodeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDependNode, kPluginNodeClassify ) except: sys.stderr.write( 'Failed to register node: ' + kPluginNodeName ) raise def uninitializePlugin( mobject ): ''' Uninitializes the plug-in ''' mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.deregisterNode( kPluginNodeId ) except: sys.stderr.write( 'Failed to deregister node: ' + kPluginNodeName )
You will notice that our call to MFnPlugin.registerNode() contains six parameters. We describe these parameters as follows:
Node Name - The name of the node. We specify this value in the arbitrarily named variable kPluginNodeName. This variable is also used in uninitializePlugin(), as well as in our error-reporting calls to sys.stderr.write().
kPluginNodeName = 'myNodeName' # The name of the node.
By registering this node name, we can create an instance of node using the following Python command:
import maya.cmds as cmds cmds.createNode( 'myNodeName' )
Node ID - A unique identification number to represent our node's type (MTypeId) among all the other node types registered in Maya. We specify this value with the arbitrarily named variable kPluginNodeId.
kPluginNodeId = OpenMaya.MTypeId( 0x87EFE ) # A unique ID associated to this node type.
It is important that this MTypeId value be unique; when Maya attempts to save and load files containing our nodes, it will label our nodes using this unique ID. If the unique ID of loaded nodes is not registered in Maya (perhaps because our plug-in isn't loaded), these nodes will fail to appear in the Dependency Graph. As explained in more detail in the MTypeId API documentation, specific numeric ranges of MTypeId are reserved for different purposes. We present a brief summary of these ranges in the table below:
Reserved MTypeId Range | Description |
---|---|
0x00000 - 0x7ffff | Plug-ins which are not intended to be widely distributed. This range is suitable for internally-developed plug-ins. |
0x80000 - 0xfffff | Example plug-ins provided with Maya and its API documentation. |
Node Creation Function Reference - A reference to the arbitrarily named nodeCreator() function. This function is responsible for creating an instance of our node and returning it as a valid Maya object pointer.
def nodeCreator(): ''' Creates an instance of our node class and delivers it to Maya as a pointer. ''' return OpenMayaMPx.asMPxPtr( myNode() )
Node Attribute Initialization Function Reference - A reference to the arbitrarily named nodeInitializer() function. This function is responsible for creating the node's set of input and output attributes.
def nodeInitializer(): ''' Defines the input and output attributes as static variables in our plug-in class. ''' # The following MFnNumericAttribute function set will allow us to create our attributes. numericAttributeFn = OpenMaya.MFnNumericAttribute() #================================== # INPUT NODE ATTRIBUTE(S) #================================== global sampleDefaultValue myNode.sampleInAttribute = numericAttributeFn.create( 'myInputAttribute', 'i', OpenMaya.MFnNumericData.kFloat, sampleDefaultValue ) numericAttributeFn.setWritable( True ) numericAttributeFn.setStorable( True ) numericAttributeFn.setHidden( False ) myNode.addAttribute( myNode.sampleInAttribute ) # Calls the MPxNode.addAttribute function. #================================== # OUTPUT NODE ATTRIBUTE(S) #================================== myNode.sampleOutAttribute = numericAttributeFn.create( 'myOutputAttribute', 'o', OpenMaya.MFnNumericData.kFloat ) numericAttributeFn.setStorable( False ) numericAttributeFn.setWritable( False ) numericAttributeFn.setReadable( True ) numericAttributeFn.setHidden( False ) myNode.addAttribute( myNode.sampleOutAttribute ) #================================== # NODE ATTRIBUTE DEPENDENCIES #================================== # If sampleInAttribute changes, the sampleOutAttribute data must be recomputed. myNode.attributeAffects( myNode.sampleInAttribute, myNode.sampleOutAttribute )
We use the MFnNumericAttribute function set to create instances of MObject which encapsulate numeric attributes. These attributes require a long and short name, as well as a type and an optional default value. We assign the created attributes as static variables of the myNode class, for example: myNode.sampleInAttribute. Assigning these objects as static variables allows us to refer to them in the myNode.compute() method.
The created attributes are also added to the node's base class via MPxNode.addAttribute(). This makes the node's attributes accessible in the Dependency Graph.
The MFnNumericAttribute.setWritable() function allows the attribute to be connected as a destination to an input source. The MFnNumericAttribute.setStorable() allows the value associated with the attribute to be saved to a file. The MFnNumeric.setReadable() function allows the value associated with this attribute to be used as input to another node's writable attribute(s). The MFnNumeric.setHidden() attribute hides the attribute in the user interface so it cannot be mistakenly connected.
The MPxNode.attributeAffects() function ensures that if the value associated with the given input changes, the node's compute() function will be called to recompute the value of the output attribute. For more information, see The Dependency Graph.
Node Type - The node type defined by the plug-in. By default, this is set to MPxNode.kDependNode, but other types, such as MPxNode.kLocatorNode, MPxNode.kDeformerNode, MPxNode.kSurfaceShape, etc, can be specified to define different behaviors in the Dependency Graph.
Node Classification - The node classification is a string which determines how the node will be categorized in various rendering-related user interfaces, such as the Create Render Node and the HyperShade windows. If no string is supplied, the node will not be considered as a rendering node, and will therefore not be displayed in these user interfaces.
kPluginNodeClassify = 'utility/general' # Where this node will be found in the Maya UI.
The following table presents a set of valid rendering node classifications:
Category | Classification String |
---|---|
2D Textures | 'texture/2d' |
3D Textures | 'texture/3d' |
Env Textures | 'texture/environment' |
Surface Materials | 'shader/surface' |
Volumetric Materials | 'shader/volume' |
Displacement Materials | 'shader/displacement' |
Lights | 'light' |
General Utilities | 'utility/general' |
Color Utilities | 'utlity/color' |
Particle Utilities | 'utility/particle' |
Image Planes | 'imageplane' |
Glow | 'postprocess/opticalFX' |
The behavior of our Dependency Graph node (myNode) is defined in a class which inherits from MPxNode.
class myNode(OpenMayaMPx.MPxNode): # Static variables which will later be replaced by the node's attributes. sampleInAttribute = OpenMaya.MObject() sampleOutAttribute = OpenMaya.MObject() def __init__(self): ''' Constructor. ''' OpenMayaMPx.MPxNode.__init__(self) def compute(self, pPlug, pDataBlock): ''' Node computation method. - pPlug: A connection point related to one of our node attributes (could be an input or an output) - pDataBlock: Contains the data on which we will base our computations. ''' if( pPlug == myNode.sampleOutAttribute ): # Obtain the data handles for each attribute sampleInDataHandle = pDataBlock.inputValue( myNode.sampleInAttribute ) sampleOutDataHandle = pDataBlock.outputValue( myNode.sampleOutAttribute ) # Extract the actual value associated to our sample input attribute (we have defined it as a float) sampleInValue = sampleInDataHandle.asFloat() # ... perform the desired computation ... # Set the output value. sampleOutDataHandle.setFloat( sampleInValue ) # As an example, we just set the output value to be equal to the input value. # Mark the output data handle as being clean; it need not be computed given its input. sampleOutDataHandle.setClean() else: return OpenMaya.kUnknownParameter
The node attributes added with MPxNode.addAttribute() in nodeInitializer() are used by Maya to define the input and output connection points of our node when it is instantiated in the Dependency Graph.
These connection points are known as plugs, and are represented by instances of MPlug. The call to MPxNode.attributeAffects() in nodeInitializer() determines the causal input > output link between these plugs. Ultimately, the Dependency Graph keeps track of the directed edges of connected plugs among all the nodes. During Maya's ongoing Dependency Graph evaluation process, if data associated to a plug is changed, this plug is marked as dirty, meaning that its cached value is stale and must be recomputed. The plug hierarchy is recursively traversed to mark the remaining affected plugs as dirty.
Maya then calls the compute() method on each node containing a dirty plug, passing an instance of MPlug as well as MDataBlock. The related attribute of the passed MPlug should be checked to determine what to compute. This is done using MPlug's overloaded equality operator '=='.
The passed MDataBlock instance contains MDataHandle objects, which store the values associated to their respective attributes. The float input value is obtained as follows:
sampleInDataHandle = pDataBlock.inputValue( myNode.sampleInAttribute ) sampleInValue = sampleInDataHandle.asFloat()
The compute() method should conclude by setting a newly computed value to the correct output MDataHandle. The call to MDataHandle.setClean() removes the dirty flag from its corresponding plug.
If the plug passed into the compute() function is not related to an output attribute we can compute, the function should return OpenMaya.kUnknownParameter. This lets Maya know that the plug's value should be computed using one of our base class's compute() functions.