It's easy to forget the workflow of implementing a custom node in Maya, so I'll write it as a reminder.
This time, I will present a node with simple input / output, a node that can handle arrays, and a node that can handle compound attributes, and finally use it to create a node that outputs the center positions of multiple points.
For custom nodes (dependency nodes, nodes with input / output), refer to the following official pages. Basics of Dependency Graph Plugin Dependency Graph Plugin
At least in the script
An empty function that tells you to use Maya Python API 2.0
If you define a function called maya_useNewAPI ()
, you can clearly indicate that you will use API 2.0 on the Maya side.
Plugin entry point / exit point
You need ʻinitializePlugin (mobject) and ʻuninitializePlugin (mobject)
to be called when loading and terminating the plugin.
Here we define ** node name **, ** node ID **, ** the following 4/5 functions **, ** node type **, ** node classification ** (see below).
Create function that returns an instance of a node
Function that initializes node attributes
Node body class
Will be needed.
The sample node that I actually wrote is as follows. It is a node that multiplies the Float value put in the input attribute by the sin function and outputs it from the output attribute. I will explain one by one.
# -*- coding: utf-8 -*-
import maya.api.OpenMaya as om
import maya.api.OpenMayaUI as omui
import math, sys
# Maya API 2.Functions required to use 0
def maya_useNewAPI():
pass
#Actual class
class sampleNode(om.MPxNode):
id = om.MTypeId(0x7f001) #Unique ID https://download.autodesk.com/us/maya/2011help/API/class_m_type_id.html
input = om.MObject()
output = om.MObject()
#Method to return an instance
@staticmethod
def creator():
return sampleNode()
#Method called by Maya at initialization
#Set attributes
@staticmethod
def initialize():
#Attributes are defined using the create method of a subclass of the MFnAttribute class.
nAttr = om.MFnNumericAttribute()
sampleNode.input = nAttr.create(
'input', 'i', om.MFnNumericData.kFloat, 0.0)
nAttr.storable = True
nAttr.writable = True
nAttr = om.MFnNumericAttribute()
sampleNode.output = nAttr.create('output', 'o', om.MFnNumericData.kFloat, 0.0)
nAttr.storable = True
nAttr.writable = True
#After defining, execute addAttribute of MPxNode
sampleNode.addAttribute(sampleNode.input)
sampleNode.addAttribute(sampleNode.output)
#Also, set the output to be recalculated when the input is changed.
sampleNode.attributeAffects( sampleNode.input, sampleNode.output)
#Constructor calls parent constructor
def __init__(self):
om.MPxNode.__init__(self)
#A method called by Maya when the value of an attribute is calculated
def compute(self, plug, dataBlock):
if(plug == sampleNode.output):
dataHandle = dataBlock.inputValue(sampleNode.input)
inputFloat = dataHandle.asFloat()
result = math.sin(inputFloat) * 10.0
outputHandle = dataBlock.outputValue(sampleNode.output)
outputHandle.setFloat(result)
dataBlock.setClean(plug)
# http://help.autodesk.com/view/MAYAUL/2016/ENU/
# api1.0 means MStatus unless you explicitly tell us not to process the plug.kUnknownParameter is not returned
# api2.At 0, there is no MStatus in the first place, so you can ignore it.
#A function called by Maya that registers a new node
def initializePlugin(obj):
mplugin = om.MFnPlugin(obj)
try:
mplugin.registerNode('sampleNode', sampleNode.id, sampleNode.creator,
sampleNode.initialize, om.MPxNode.kDependNode)
except:
sys.stderr.write('Faled to register node: %s' % 'sampleNode')
raise
#Functions called by Maya when exiting the plug-in
def uninitializePlugin(mobject):
mplugin = om.MFnPlugin(mobject)
try:
mplugin.deregisterNode(sampleNode.id)
except:
sys.stderr.write('Faled to uninitialize node: %s' % 'sampleNode')
raise
First from the class definition
class sampleNode(om.MPxNode):
id = om.MTypeId(0x7f001) #Unique ID https://download.autodesk.com/us/maya/2011help/API/class_m_type_id.html
input = om.MObject()
output = om.MObject()
Decide the id of the plug-in. For more information, see Autodesk Site, but usually you can use any value from 0x00000 to 0x7ffff. I will.
And I prepared the input / output attributes as the class field ʻintput ʻoutput
.
#Method to return an instance
@staticmethod
def creator():
return sampleNode()
A function that instantiates a node. You can write it outside the class, but this time I wrote it as a static method of the class.
#Method called by Maya at initialization
#Set attributes
@staticmethod
def initialize():
#Attributes are defined using the create method of a subclass of the MFnAttribute class.
nAttr = om.MFnNumericAttribute()
sampleNode.input = nAttr.create(
'input', 'i', om.MFnNumericData.kFloat, 0.0)
nAttr.storable = True
nAttr.writable = True
nAttr = om.MFnNumericAttribute()
sampleNode.output = nAttr.create('output', 'o', om.MFnNumericData.kFloat, 0.0)
nAttr.storable = True
nAttr.writable = True
#After defining, execute addAttribute of MPxNode
sampleNode.addAttribute(sampleNode.input)
sampleNode.addAttribute(sampleNode.output)
#Also, set the output to be recalculated when the input is changed.
sampleNode.attributeAffects( sampleNode.input, sampleNode.output)
This is the part corresponding to "4. Function that initializes node attributes" in the previous list. You can write it outside the class, but it's cluttered, so I wrote it as a static method.
Attributes are defined using the appropriate subclasses of the MFnAttribute
class. This time it's a Float value, so I'm using MFnNumericAttribute
. Three-dimensional float values (coordinates, etc.) and bool values are also this MFnNumericAttribute
. You can find the angle, distance, and time from MFnUnitAttribute
, the matrix from MFnMatrixAttribute
, and the others from the reference below.
MFnAttribute Class Reference
OpenMaya.MFnAttribute Class Reference
Specify the attribute name, abbreviation, type, and initial value with nAttr.create
.
Whether nAttr.storable
writes the value of the attribute to the save file. There are other properties such as writable
and readable
, so set them appropriately.
sampleNode.addAttribute (sampleNode.input)
Adds the created attribute to the node.
sampleNode.attributeAffects (sampleNode.input, sampleNode.output)
When the value of the input attribute changes, the output attribute will be updated.
#A method called by Maya when the value of an attribute is calculated
def compute(self, plug, dataBlock):
if(plug == sampleNode.output):
dataHandle = dataBlock.inputValue(sampleNode.input)
inputFloat = dataHandle.asFloat()
result = math.sin(inputFloat) * 10.0
outputHandle = dataBlock.outputValue(sampleNode.output)
outputHandle.setFloat(result)
dataBlock.setClean(plug)
The compute method is the method that is called when the calculation is done. This node is calculating Sin. The value is passed in the form of a plug. For more information on plugins, see Autodesk Help (https://help.autodesk.com/view/MAYAUL/2016/JPN/?guid=__files_Dependency_graph_plugins_Attributes_and_plugs_htm). It's long because it takes a handle from the DataBlock to get the input and assign it to the output, but it's actually just computing the Sin function.
#A function called by Maya that registers a new node
def initializePlugin(obj):
mplugin = om.MFnPlugin(obj)
try:
mplugin.registerNode('sampleNode', sampleNode.id, sampleNode.creator,
sampleNode.initialize, om.MPxNode.kDependNode)
except:
sys.stderr.write('Faled to register node: %s' % 'sampleNode')
raise
An entry point outside the class. Specify the node name and ID, the instance method defined in the class, and the node type.
Load the script you created from Maya's Plug-in Manager.
Create a node with cmds.createNode ('sampleNode')
or cmds.shadingNode ('sampleNode', asUtility = True)
on the Maya command line or script editor. If you created it with the latter, your node will be displayed in the tab called "Utilities" in the hypershade window.
There are two methods, one is to connect the array data to the plug as one attribute, and the other is to use the ** array plug ** in which the plug itself is an array.
Change the definition of the attribute in the ʻinitializemethod earlier as follows.
nAttr.array = True is ok, but by setting
nAttr.indexMatters = Falseto False, you can use
-nextAvailable with the
connectAttr` command. On the contrary, if it is True, it seems that the index to be inserted must be specified.
@staticmethod
def initialize():
nAttr = om.MFnNumericAttribute()
sampleArrayNode.input = nAttr.create(
'input', 'i', om.MFnNumericData.kFloat, 0.0)
nAttr.storable = True
nAttr.writable = True
nAttr.readable = True
nAttr.array = True #add to
nAttr.indexMatters = False #add to
Next, the compute
method that is responsible for the actual calculation. This time, the process is to get the total value of the input array.
def compute(self, plug, dataBlock):
arrayDataHandle = dataBlock.inputArrayValue(
sampleArrayNode.input
)
sum = 0
while not arrayDataHandle.isDone():
handle = arrayDataHandle.inputValue()
v = handle.asFloat()
sum += v
arrayDataHandle.next()
outhandle = dataBlock.outputValue( sampleArrayNode.output )
outhandle.setFloat(sum)
dataBlock.setClean(plug)
When using an array plug, get the MArrayDataHandle
once with ʻinputArrayValue instead of ʻinputValue
.
Since this is an iterator, use next ()
or jumpToLogicalElement ()
to advance the iterator and ʻarrayDataHandle.inputValue ()` to get the value of the array element. After that, it is converted to a numerical value and calculated in the same way as a normal plug.
↑ The constant 1 + 2 + 3 + 4 = 10 was calculated correctly.
A composite attribute is a collection of multiple attributes. Complex dynamic attributes In this example, "coordinates and weights" are used as compound attributes, and they are used as array plugs.
It looks like the image below on the node editor. The implementation of the class part is as follows. The entry point etc. are the same as the previous code (omitted).
class sampleArrayNode(om.MPxNode):
#Unique ID https://download.autodesk.com/us/maya/2011help/API/class_m_type_id.html
id = om.MTypeId(0x7f011)
input = om.MObject()
output = om.MObject()
#Child attributes
position = om.MObject()
weight = om.MObject()
#Method to return an instance
@staticmethod
def creator():
return sampleArrayNode()
#Method called by Maya at initialization
#Set attributes
@staticmethod
def initialize():
#Child attributes
#Coordinate
nAttr = om.MFnNumericAttribute()
sampleArrayNode.position = nAttr.create(
'position', 'pos', om.MFnNumericData.k3Float, 0
)
nAttr.readable = True
#weight
nAttr = om.MFnNumericAttribute()
sampleArrayNode.weight = nAttr.create(
'weight', 'w', om.MFnNumericData.kFloat, 1
)
nAttr.readable = True
nAttr.setMax(1) # Min,Max can also be specified
nAttr.setMin(0)
#Composite attributes
nAttr = om.MFnCompoundAttribute()
sampleArrayNode.input = nAttr.create(
'input', 'i')
nAttr.readable = True
nAttr.array = True
nAttr.indexMatters = False
nAttr.addChild(sampleArrayNode.position)
nAttr.addChild(sampleArrayNode.weight)
#The output is coordinates this time (3D Float)
nAttr = om.MFnNumericAttribute()
sampleArrayNode.output = nAttr.create(
'output', 'o', om.MFnNumericData.k3Float)
nAttr.storable = True
nAttr.writable = True
nAttr.readable = True
#After defining, execute addAttribute of MPxNode
sampleArrayNode.addAttribute(sampleArrayNode.input)
sampleArrayNode.addAttribute(sampleArrayNode.output)
#Also, set the output to be recalculated when the input is changed.
sampleArrayNode.attributeAffects(
sampleArrayNode.input, sampleArrayNode.output)
#Constructor calls parent constructor
def __init__(self):
om.MPxNode.__init__(self)
#A method called by Maya when the value of an attribute is calculated
def compute(self, plug, dataBlock):
arrayDataHandle = dataBlock.inputArrayValue(
sampleArrayNode.input
)
sumX = 0
sumY = 0
sumZ = 0
num = len(arrayDataHandle)
while not arrayDataHandle.isDone():
#Data handle for composite attributes
dataHandle = arrayDataHandle.inputValue()
# .You can get child attributes with child
childHandle = dataHandle.child(
sampleArrayNode.position
)
pos = childHandle.asFloat3()
childHandle = dataHandle.child(
sampleArrayNode.weight
)
w = childHandle.asFloat()
sumX += pos[0] * w
sumY += pos[1] * w
sumZ += pos[2] * w
arrayDataHandle.next()
outhandle = dataBlock.outputValue(sampleArrayNode.output)
if(num != 0):
outhandle.set3Float(sumX / num, sumY / num, sumZ / num)
else:
outhandle.set3Float(0, 0, 0)
dataBlock.setClean(plug)
# http://help.autodesk.com/view/MAYAUL/2016/ENU/
# api1.0 means MStatus unless you explicitly tell us not to process the plug.kUnknownParameter is not returned
# api2.At 0, there is no MStatus in the first place, so you can ignore it.
#A function called by Maya that registers a new node
What has changed is that the variables position
and weight
are prepared as class fields. Since these are used as child attributes of compound attributes, they are defined in the ʻinitialize method in the same way as normal attributes. The composite attribute that puts these together is ʻinput
.
#Composite attributes
nAttr = om.MFnCompoundAttribute()
sampleArrayNode.input = nAttr.create(
'input', 'i')
nAttr.readable = True
nAttr.array = True
nAttr.indexMatters = False
nAttr.addChild(sampleArrayNode.position) #← This is the point
nAttr.addChild(sampleArrayNode.weight)
The difference from normal attributes is that the class of the attribute is MFnCompoundAttribute
and the child attribute defined above in .addChild
is added.
To use a composite attribute within a compute method
#Data handle for composite attributes
dataHandle = arrayDataHandle.inputValue()
# .You can get child attributes with child
childHandle = dataHandle.child(
sampleArrayNode.position
)
pos = childHandle.asFloat3()
childHandle = dataHandle.child(
sampleArrayNode.weight
)
w = childHandle.asFloat()
Use the .child
method from the composite attribute data handle to get and access the child attribute data handle.
When you actually use the created node, it looks like this. Connect the positions of multiple objects to a node. Try connecting the output to the locator. The locator has moved to the center of the sphere, cone, and cube. This time, I added a weight value in addition to the coordinates as a compound attribute, so let's use it. If you lower the weight of the sphere ... The influence of the sphere has disappeared and the locator has moved to the center of the cone and cube.
Recommended Posts