I saw a post on www.cgtalk.com requesting an example of how to create a compound attribute on a node. Here is a full working example with a detailed description of how it works following the example:
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
kcompUtilNodeTypeName = "compoundExampleNode"
kcompUtilNodeClassify = "utility/general"
kcompUtilNodeId = OpenMaya.MTypeId(0x87325)
# define a new matrixUtilNode class derived from the MPxNode class
class rtcompUtilNode(OpenMayaMPx.MPxNode):
# class variables
compIn = OpenMaya.MObject()
compOut = OpenMaya.MObject()
input = OpenMaya.MObject()
output = OpenMaya.MObject()
def __init__(self):
OpenMayaMPx.MPxNode.__init__(self)
# arguments ( self, MPlug, MDataBlock)
def compute(self, plug, dataBlock):
# if these attributes are requested, recompute their values
if plug == rtcompUtilNode.compOut or plug.parent() == rtcompUtilNode.compOut:
# get MDataHandle's to attributes
#
try:
input_dataHandle = dataBlock.inputValue( rtcompUtilNode.input )
except:
sys.stderr.write( "Failed to get MDataHandle inputValue input" )
raise
try:
output_dataHandle = dataBlock.outputValue( rtcompUtilNode.output )
except:
sys.stderr.write( "Failed to get MDataHandle outputValue output" )
raise
# get values from dataHandle
#
input_value = input_dataHandle.asFloat()
# set the output
output_dataHandle.setFloat(input_value)
# set the plug clean so maya knows it can update
dataBlock.setClean(plug)
else:
return OpenMaya.kUnknownParameter
return OpenMaya.MStatus.kSuccess
def nodeCreator():
return OpenMayaMPx.asMPxPtr( rtcompUtilNode() )
# create and initialize the attributes to the node
def nodeInitializer():
nAttr = OpenMaya.MFnNumericAttribute()
cAttr = OpenMaya.MFnCompoundAttribute()
# create input attributes
#
rtcompUtilNode.input = nAttr.create("input", "i", OpenMaya.MFnNumericData.kFloat, 0.0)
nAttr.setWritable(True)
nAttr.setStorable(True)
nAttr.setReadable(True)
nAttr.setKeyable(True)
rtcompUtilNode.output = nAttr.create("output", "o", OpenMaya.MFnNumericData.kFloat, 0.0)
nAttr.setWritable(False)
nAttr.setStorable(False)
nAttr.setReadable(True)
# create compound attribute
#
rtcompUtilNode.compIn = cAttr.create( "compIn", "ci" )
cAttr.addChild( rtcompUtilNode.input )
rtcompUtilNode.compOut = cAttr.create( "compOut", "co" )
cAttr.addChild( rtcompUtilNode.output )
# add attribues
#
rtcompUtilNode.addAttribute( rtcompUtilNode.compIn )
rtcompUtilNode.addAttribute( rtcompUtilNode.compOut )
# Setup which attributes affect each other
rtcompUtilNode.attributeAffects ( rtcompUtilNode.compIn, rtcompUtilNode.compOut )
# initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "1.0", "Any")
try:
mplugin.registerNode( kcompUtilNodeTypeName, kcompUtilNodeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDependNode, kcompUtilNodeClassify)
except:
sys.stderr.write( "Failed to register node: %s" % kcompUtilNodeTypeName )
raise
# uninitialize the script plug-in
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterNode( kcompUtilNodeId )
except:
sys.stderr.write( "Failed to deregister node: %s" % kcompUtilNodeTypeName )
raise
First lets get it working before I talk about how the script works. Save this script as a file somewhere on your computer with a .py extension. In maya select the menu Window/Settings and Preferences/Plug-in Manager. Click the browse button in the plug-in manager and select the file that you saved the script as. This will a plug-in called compountAttrExample to the “OtherRegisteredPlugins†section of the manager. If you click on the “i†icon to the right of it, the plug-in shows the Dependency nodes of that plug-in. This lets you see what the plug-in has created. Now lets create the new node we added.
Open the script editor, and in a python tab run these lines of script:
import maya.cmds as cmds cmdAttrNode = cmds.createNode(’compoundExampleNode’) sphere = cmds.polySphere() cmds.connectAttr( (sphere[1] + ‘.radius’), (cmdAttrNode + ‘.input’), force=True) cmds.getAttr( cmdAttrNode + ‘.output’)
This creates a new compoundExampleNode and a poly sphere. The poly sphere has its radius attribute connected to the input attribute of the compoundExampleNode. Finally we get the attribute value of the output from the compoundExampleNode. If the sphere’s radius changes it will automatically change the output.
Now lets break down the script.
import maya.OpenMaya as OpenMaya import maya.OpenMayaMPx as OpenMayaMPx kcompUtilNodeTypeName = "compoundExampleNode" kcompUtilNodeClassify = "utility/general" kcompUtilNodeId = OpenMaya.MTypeId(0×87325)
First we must import maya.OpenMaya nd maya.OpenMayaMPx. These give us access to the Maya api classes we need for creating the plug-in. Most maya.OpenMaya classes can be used in the script editor in in a general Python script, but maya.OpenMayaMPx classes are specifically used for creating nodes which you must create a plug-in to use.
Next the kcompUtilNodeTypeName defines the name of the plugin node. This is just a variable placed at the top of the script so it is easy to change. The kcompUtilNodeClassify variable allows you to classify your node. If a node is in the utility/general class it will show up in the hypershade under general utilities. You can get the class of any specific Maya node by using
cmds.getClassification( ‘nodeName’ )
The last variable defines the node id number. Maya needs this to understand which node is which. If your class id is the same as another node it will conflict and your node will not work.
Moving on to the next part we define the nodes class and figure out what we derive from:
class rtcompUtilNode(OpenMayaMPx.MPxNode): ...
First we define the class rtcompUtilNode which derives from the class OpenMayaMPx.MPxNode Maya defines what is needed for a non dag node inside of the MPxNode class. If you want to create a node that has a transform you need to derive from another class like MPxLocatorNode for instance.
Next we define the variables of that class.
# class variables compIn = OpenMaya.MObject() compOut = OpenMaya.MObject() input = OpenMaya.MObject() output = OpenMaya.MObject()
You can have general variables you need for doing whatever it is you want to do, but these variables are special. They define OpenMaya.MObject() attributes which is what you will use to create input / output attributes on your node.
Next we move onto compute:
def compute(self, plug, dataBlock): # if these attributes are requested, recompute their values if plug == rtcompUtilNode.compOut or plug.parent() == rtcompUtilNode.compOut:
This function is run anytime an attribute on the node changes. The if plug statement changes to see if the compOut attribute has been requested. Then you know you have the attribute that you want to update.
The last part of the class looks like this:
try: input_dataHandle = dataBlock.inputValue( rtcompUtilNode.input ) except: sys.stderr.write( "Failed to get MDataHandle inputValue input" ) raise try: output_dataHandle = dataBlock.outputValue( rtcompUtilNode.output ) except: sys.stderr.write( "Failed to get MDataHandle outputValue output" ) raise # get values from dataHandle # input_value = input_dataHandle.asFloat() # set the output output_dataHandle.setFloat(input_value) # set the plug clean so maya knows it can update dataBlock.setClean(plug)
Here we get the input and output attributes as MDataHandle objects. The MDataHandle class lets you get and set data on a attribute.
input_dataHandle = dataBlock.inputValue( rtcompUtilNode.input ) output_dataHandle = dataBlock.outputValue( rtcompUtilNode.output )
Once we have the MDataHandle object we can query the value on the input attribute like this. The MDataHandle class has several ways of querying data. You must use the correct function to work with the type of data you have stored. asFloat() does not work for int’s ect. You would use asInt() for an int. See the documentation on the class for more details.
input_value = input_dataHandle.asFloat()
And our last step is to set the output attribute. First we set the attribute again with a specific function for setting floats that the MDataHandle class has. Then we use setClean(plug) to tell Maya that we updated the attribute and the node is ready.
output_dataHandle.setFloat(input_value) dataBlock.setClean(plug)
Every node needs nodeCreator() function. This simply returns the node that you have setup.
def nodeCreator()
The nodeInitializer function is what sets up the nodes attributes. First we start off by defining the types of attributes we want to create.
nAttr = OpenMaya.MFnNumericAttribute() cAttr = OpenMaya.MFnCompoundAttribute()
Next we create some numeric attributes.
rtcompUtilNode.input = nAttr.create("input", "i", OpenMaya.MFnNumericData.kFloat, 0.0)
nAttr.setWritable(True)
nAttr.setStorable(True)
nAttr.setReadable(True)
nAttr.setKeyable(True)
rtcompUtilNode.output = nAttr.create("output", "o", OpenMaya.MFnNumericData.kFloat, 0.0)
nAttr.setWritable(False)
nAttr.setStorable(False)
nAttr.setReadable(True)
Then we create two compound attributes. Note we create the compound attribute and then add the attribute that we want to be a child of that compound attribute. Once we add a attribute to a compound attribute we do not add it again.
rtcompUtilNode.compIn = cAttr.create( "compIn", "ci" ) cAttr.addChild( rtcompUtilNode.input ) rtcompUtilNode.compOut = cAttr.create( "compOut", "co" ) cAttr.addChild( rtcompUtilNode.output )
Now we add the compound attributes.
rtcompUtilNode.addAttribute( rtcompUtilNode.compIn ) rtcompUtilNode.addAttribute( rtcompUtilNode.compOut )
Our last step is we tell Maya that we want the input compound attribute to affect the output. If the input changes it will tell the node to call the compute function. Any attribute inside of the compound attribute will cause compute to be called. Also note it is ok to have many attributes that affect many attributes. You can save yourself some trouble though by creating fewer compound attributes to group attributes that are similar.
rtcompUtilNode.attributeAffects ( rtcompUtilNode.compIn, rtcompUtilNode.compOut )
Almost done!
Now we need to make it so Maya knows how to create the node and uncreate the node. This is what the node id and name and all the above that we created comes together as a package.
When you call the command createNode Maya calls teh initializePlugin function of this node. This attempts to register this node with the name, id, nodeCreator function, nodeInitializer function, type of maya node, and the class we defined it as. The node
def initializePlugin(mobject): mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "1.0", "Any") try: mplugin.registerNode( kcompUtilNodeTypeName, kcompUtilNodeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDependNode, kcompUtilNodeClassify) except: sys.stderr.write( "Failed to register node: %s" % kcompUtilNodeTypeName ) raise
This just does the opposite and removes the node when you delete it from Maya using the node id that it has.
def uninitializePlugin(mobject): mplugin = OpenMayaMPx.MFnPlugin(mobject) try: mplugin.deregisterNode( kcompUtilNodeId ) except: sys.stderr.write( "Failed to deregister node: %s" % kcompUtilNodeTypeName ) raise
Wow that takes a bit to describe but I hope it helps.
-RyanT
July 14th, 2010 at 8:40 pm
Keep posting stuff like this i really like it
July 28th, 2010 at 2:58 am
Ryan, can you help me? I try to add Boolean attribute to a node, but Maya write an error: # Error: RuntimeError: (kInvalidParameter): The data is not of that type #
my code:
nAttr = OpenMaya.MFnNumericAttribute()
x = nAttr.create( “Bla”, “bla”, OpenMaya.MFnNumericData.kBoolean, True )
nAttr.setDefault( True )
Thanks for help.
July 29th, 2010 at 4:57 pm
I think your close. Just make sure you have created your attribute as part of your class also the class name should come before the x. It should look something like this.
class yourClassNodeName(OpenMayaMPx.MPxNode):
# class variables
myBoolAttr = OpenMaya.MObject()
def __init__(self):
OpenMayaMPx.MPxNode.__init__(self)
def nodeInitializer():
nAttr = OpenMaya.MFnNumericAttribute()
yourClassNodeName.myBoolAttr = nAttr.create(”nodeState”, “ns”, OpenMaya.MFnNumericData.kBoolean , 0)
nAttr.setWritable(True)
nAttr.setStorable(True)
nAttr.setReadable(True)
nAttr.setKeyable(True)