It is possible to write basic scripts that use the wrapper, iterator and function set classes of the Maya API. These scripts can query and manipulate the Maya model but are not fully integrated into Maya. A scripted plug-in provides a more complex solution that is tightly integrated into Maya. In this section, we discuss how to write both basic and scripted plug-in scripts along with standalone scripts.
As this is a Python based API, knowledge of Python is required.
The Maya Python API is contained in a number of Python modules. You must import the functionality that you wish to use in your script. Additionally, the Maya Python API lives in the Maya namespace; therefore, an extra prefix is required. To import the OpenMaya module, run the following:
import maya.OpenMaya
Information can be displayed about any of the modules or classes using the help command. For example, if you wish to display the class information for MVector, use:
It is also possible to display the information of an entire module:
This operation will take a while to return since the OpenMaya module is very large.
The Maya Python API modules contain the classes that are available for Python programming. These classes are separated into different categories and have appropriate naming conventions to signify their association. Classes include:
These classes are iterators and work on MObjects similar to the way a function set does. For example, MItCurveCV is used to operate on an individual NURBS curve CV (there is no MFnNurbsCurveCV), or, iteratively, on all the CVs of a curve.
Classes with this prefix are all “Proxies”, that is, API classes designed for you to derive from and create your own object types.
Most, although not all, of these classes are “Wrappers”. Examples of this class are: MVector, MIntArray, and so forth.
We can use wrapper and function set classes to write scripts such as the following:
import maya.OpenMaya
vector1 = maya.OpenMaya.MVector(0,1,0)
vector2 = maya.OpenMaya.MVector(1,0,0)
vector3 = maya.OpenMaya.MVector(0,0,2)
newVector = vector1 + vector2 + vector3
print "newVector %f, %f, %f " % (newVector.x, newVector.y, newVector.z)
It is possible to shorten the symbol names used by modifying the import command:
import maya.OpenMaya as OpenMaya
vector1 = OpenMaya.MVector(0,1,0)
Scripts can access dependency graph information using the Maya Python API classes. The following is a script that finds the persp node and prints out its translateX attribute value:
# import the OpenMaya module
import maya.OpenMaya as OpenMaya
# function that returns a node object given a name
def nameToNode( name ):
selectionList = OpenMaya.MSelectionList()
selectionList.add( name )
node = OpenMaya.MObject()
selectionList.getDependNode( 0, node )
return node
# function that finds a plug given a node object and plug name
def nameToNodePlug( attrName, nodeObject ):
depNodeFn = OpenMaya.MFnDependencyNode( nodeObject )
attrObject = depNodeFn.attribute( attrName )
plug = OpenMaya.MPlug( nodeObject, attrObject )
return plug
# Find the persp camera node
print "Find the persp camera";
perspNode = nameToNode( "persp" )
print "APItype %d" % perspNode.apiType()
print "APItype string %s" % perspNode.apiTypeStr()
# Print the translateX value
translatePlug = nameToNodePlug( "translateX", perspNode )
print "Plug name: %s" % translatePlug.name()
print "Plug value %g" % translatePlug.asDouble()
The example above demonstrates the following:
Scripted plug-ins allow a developer to create a solution that is tightly coupled with Maya. Scripted plug-ins allow a developer to support functionality such as the undoing of commands and the building of appropriate requires lines into the Maya scene file. Another advantage of using a scripted plug-in is that its functionality is available in both MEL and Python.
We have extended the Maya Plug-in Manager to support the loading and unloading of scripted plug-ins.
Any file ending with a .py extension that is on the MAYA_PLUG_IN_PATH is displayed in the Plug-in Manager. Select the Loaded check-box or the Auto load check box to either load or auto-load the scripted plug-in.
The plug-in can either be loaded from the Plug-in Manager or from the MEL or Python command tabs. In MEL, use the loadPlugin() command. In Python, use the maya.cmds.loadPlugin() command
To run an example such as helixCmd.py, load the plug-in and enter the following in the Python Editor tab:
import maya
maya.cmds.spHelix().
Invoking this Python script does the following:
This plug-in can also be unloaded using the Python command: maya.cmds.unloadPlugin(“helixCmd.py”)
The load and execute steps can also be invoked in the MEL editor using:
loadPlugin helixCmd.py;
spHelix();
Writing a scripted plug-in requires the definition of some specialized functions within the plug-in. The scripted plug-in must:
The following sections describe these pieces in more detail with examples.
Python uses the import keyword to include functionality from a module into a script. For example:
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import sys
It is possible for a scripted plug-in to be split among several files. The import command is used to load the functionality of the secondary file into the scripted plug-in.
import polyModifier
Any secondary scripts must be located in the same directory as the scripted plug-in.
Scripted plug-in initialization
When a scripted plug-in is loaded, Maya searches for an initializePlugin() function in its definition. Within this function, all proxy nodes are registered:
# Initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.registerCommand( kPluginCmdName, cmdCreator )
except:
sys.stderr.write( "Failed to register command: %s\n" % kPluginCmdName )
raise
If the initializePlugin() function is not found, the scripted plug-in fails to load. In addition, during the load, Maya searches for an uninitializePlugin() function. If this is not found, then the scripted plug-in fails to load.
Scripted plug-in uninitialization
When Maya is attempting to unload the plug-in, the previously found uninitializePlugin() function is called to unload the resources of the plug-in.
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterCommand( kPluginCmdName )
except:
sys.stderr.write( "Failed to unregister command: %s\n" % kPluginCmdName )
raise
Creator functions are used to return a derived version of a proxy class to Maya. Virtual methods are implemented on the derived class which are called from Maya. An example of a class definition and a creator function is:
class scriptedCommand(OpenMayaMPx.MPxCommand):
# ...
def cmdCreator():
return OpenMayaMPx.asMPxPtr( scriptedCommand() )
It is very important to call the OpenMayaMPx.asMPxPtr() on the newly created proxy object. This call transfers ownership of the object from Python to Maya. Program errors will occur if you do not make this call since Python can unreference this object and destroy it. This will leave a dangling pointer in Maya.
Implementing a proxy class requires deriving from the Maya Python API object.
class scriptedCommand(OpenMayaMPx.MPxCommand):
def __init__(self):
OpenMayaMPx.MPxCommand.__init__(self)
def doIt(self,argList):
print "Hello World!"
The scriptedCommand class is derived from OpenMayaMPx.MPxCommand. The constructor or __init__ method must call the parent class __init__ method. All class methods require self as the first parameter, followed by the normal argument list. This command’s doIt() method simply prints out “Hello World!”.
Initialization functions are used within scripted plug-ins that define new proxy nodes using the MPxNode class. The following is an example that demonstrates how to create a simple scripted plug-in node, the output of which is the sine function.
import math, sys
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
kPluginNodeTypeName = "spSineNode"
sineNodeId = OpenMaya.MTypeId(0x8700)
# Node definition
class sineNode(OpenMayaMPx.MPxNode):
# class variables
input = OpenMaya.MObject()
output = OpenMaya.MObject()
def __init__(self):
OpenMayaMPx.MPxNode.__init__(self)
def compute(self,plug,dataBlock):
if ( plug == sineNode.output ):
dataHandle = dataBlock.inputValue( sineNode.input )
inputFloat = dataHandle.asFloat()
result = math.sin( inputFloat ) * 10.0
outputHandle = dataBlock.outputValue( sineNode.output )
outputHandle.setFloat( result )
dataBlock.setClean( plug )
# creator
def nodeCreator():
return OpenMayaMPx.asMPxPtr( sineNode() )
# initializer
def nodeInitializer():
# input
nAttr = OpenMaya.MFnNumericAttribute();
sineNode.input = nAttr.create( "input", "in", OpenMaya.MFnNumericData.kFloat, 0.0 )
nAttr.setStorable(1)
# output
nAttr = OpenMaya.MFnNumericAttribute();
sineNode.output = nAttr.create( "output", "out", OpenMaya.MFnNumericData.kFloat, 0.0 )
nAttr.setStorable(1)
nAttr.setWritable(1)
# add attributes
sineNode.addAttribute( sineNode.input )
sineNode.addAttribute( sineNode.output )
sineNode.attributeAffects( sineNode.input, sineNode.output )
# initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.registerNode( kPluginNodeTypeName, sineNodeId, nodeCreator, nodeInitializer )
except:
sys.stderr.write( "Failed to register node: %s" % kPluginNodeTypeName )
raise
# uninitialize the script plug-in
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterNode( sineNodeId )
except:
sys.stderr.write( "Failed to register node: %s" % kPluginNodeTypeName )
raise
The nodeInitializer() function is passed to registerNode() in the initializePlugin() function. As the plug-in loads, Maya calls the nodeInitializer() function to create the attributes of the node.
The Maya Python API uses Python exceptions for querying and setting error states in script. In most cases, exceptions are used even though the class documentation indicates that a method has a return value. There are many situations where an exception can occur:
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterNode( sineNodeId )
except:
sys.stderr.write( "Failed to deregister node: %s" % kPluginNodeTypeName )
raise
In this example, if the deregisterNode() call failed, the uninitializePlugin() call passes the exception back to Maya and the plug-in fails to unload.
This code can be modified to catch the error and still allow the plug-in to unload if the deregisterNode() call fails:
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterNode( sineNodeId )
except:
sys.stderr.write( "Failed to deregister node: %s" % kPluginNodeTypeName )
pass
The only change being that the raise keyword has been changed to pass. This technique is useful for writing iterator code that may fail if incorrect objects are being examined.
3. Unknown parameter return value
In the Maya Python API, an unknown parameter return value is used to indicate that a method cannot handle a specific case and it is up to the caller to take care of the operation. One such method is MPxNode::compute(). In this situation, the Python code would return OpenMaya.kUnknownParameter.
As noted in the previous section, the Maya Python API uses exceptions to communicate status information rather than MStatus values. When trying to divine the Pythonic behavior of an API method from its C++ documentation, the rules below hold.
There are two ways in which C++ API methods can return MStatus values, either as the method's return value:
MStatus someMethod(Type arg1, Type arg2, ...)
or through an optional pointer to an MStatus variable in its parameter list, usually as the final parameter:
Type someMethod(Type arg1, Type arg2, ..., MStatus* ReturnStatus = NULL)
Method returns MStatus as its function value
When the method returns an MStatus as its function value, that return value is handled in Python as follows:
The reason for the special handling of MS::kUnknownParameter is to accomodate MPxNode::compute().
There are no special API-specific exceptions. Maya simply uses Python's standard RuntimeError and passes the error string as its argument.
Method returns MStatus through a pointer variable
When an API method returns an MStatus through a pointer variable in its parameter list, then that MStatus is handled as follows:
This means that users writing plug-ins in C++ can continue to return MStatus codes as they normally would, regardless of whether their code is called from C++ or Python. Maya will internally convert those codes to Python exceptions if necessary.
Users writing plug-ins in Python should raise exceptions rather than return MStatus values, unless they want their compute() method to indicate that it is not going to handle a plug. In this case, it should return maya.OpenMaya.kUnknownParameter.
All of the number arrays (MIntArray, MUintArray, MUint64Array, MFloatArray, and MDoubleArray) support Python-style slicing. For example:
import maya.OpenMaya as OpenMaya array = OpenMaya.MUintArray() for i in range(0,9): array.append( i )
array[2:8:2]
Accessing static MObjects of an MPx class
The proxy classes provide some standard information to a developer about the node that is being used. This includes attribute objects that are used to define the node. To access a static class MObject in the Maya Python API, similar code can be used:
envelope = OpenMayaMPx.cvar.MPxDeformerNode_envelope
After making this call, the envelope will be an MObject for MPxDeformerNode::envelope.
Message classes are supported in the Maya Python API. A Python function is passed for the callback. This function must have the exact number of parameters required by the callback message. If it does not, an exception will occur when the message is invoked and information will be written to the console. Client data in the form of a python object can also be passed with most messages. The following is an example of a message:
# Message callback
def dagParentAddedCallback( child, parent, clientData ):
print "dagParentAddedCallback..."
print "\tchild %s" % child.fullPathName()
print "\tparent %s" % parent.fullPathName()
print "\tclient data %s" % clientData
# Create the mesage
def createParentAddedCallback(stringData):
try:
id = OpenMaya.MDagMessage.addParentAddedCallback( dagParentAddedCallback, stringData )
except:
sys.stderr.write( "Failed to install dag parent added callback\n" )
messageIdSet = False
else:
messageIdSet = True
return id
# Call the message creator
messageId = createParentAddedCallback( "_noData_" )
Modify Parameter Values Instead of Using an Assignment
In Python, it is best to modify a parameter rather than use an assignment. The code below contains an assignment and demonstrates how an error can occur:
import maya.OpenMaya as OpenMaya
def vectorTest(v):
lv = OpenMaya.MVector(1,5,9)
v = lv
print "%g %g %g" % (v.x,v.y,v.z)
v = OpenMaya.MVector()
vectorTest(v)
print “%g %g %g” % (v.x,v.y,v.z)
The second print command will emit all zeroes. In Python, either modify the parameter value or write the code so that a new value is returned. Rewrite the vectorTest() function as follows:
def vectorTest(v):
lv = OpenMaya.MVector(1,5,9)
v.x = lv.x
v.y = lv.y
v.z = lv.z
print "%g %g %g" % (v.x,v.y,v.z)
The Maya Python API contains many calls in which return values or parameters are references to basic types such as: int&, char&, float& etc. In the Maya Python API, all references are treated as pointers. As a result, special calls are required to create, set and access the values of these items.
A utility class called MScriptUtil that exists in the OpenMaya.py module provides methods for working with these type of parameters and return values. This class allows for creating data and then acquiring a pointer to the data so that information can be passed to class methods requiring references. See MScriptUtil for more information .
The following example should help to clarify the usage of MScriptUtil. It shows how to get the x, y and z values from a call to MFnLattice.getDivisions():
import maya.OpenMaya as OpenMaya
import maya.OpenMayaAnim as Anim
xutil = OpenMaya.MScriptUtil()
xutil.createFromInt(0)
xptr = xutil.asUintPtr()
yutil = OpenMaya.MScriptUtil()
yutil.createFromInt(0)
yptr = yutil.asUintPtr()
zutil = OpenMaya.MScriptUtil()
zutil.createFromInt(0)
zptr = zutil.asUintPtr()
it = OpenMaya.MItDependencyNodes(om.MFn.kFFD)
while not it.isDone():
latDefFn = Anim.MFnLatticeDeformer( it.thisNode() )
latFn = Anim.MFnLattice( latDefFn.deformLattice() )
latFn.getDivisions(xptr, yptr, zptr)
x = xutil.getUint(xptr)
y = yutil.getUint(yptr)
z = zutil.getUint(zptr)
doSomethingUseful(x, y, z)
it.next()
Since getUint() is a static method of MScriptUtil, an alternative way of retrieving the final values is to call it directly from the class. For example:
x = OpenMaya.MScriptUtil.getUint(xptr)
Commands with arguments must use the MSyntax and MArgParser classes within a scripted MPxCommand. See the following code for an example:
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import sys, math
kPluginCmdName="spHelix"
kPitchFlag = "-p"
kPitchLongFlag = "-pitch"
kRadiusFlag = "-r"
kRadiusLongFlag = "-radius"
# command
class scriptedCommand(OpenMayaMPx.MPxCommand):
def __init__(self):
OpenMayaMPx.MPxCommand.__init__(self)
def doIt(self, args):
deg = 3
ncvs = 20
spans = ncvs - deg
nknots = spans+2*deg-1
radius = 4.0
pitch = 0.5
# Parse the arguments.
argData = OpenMaya.MArgDatabase(self.syntax(), args)
if argData.isFlagSet(kPitchFlag):
pitch = argData.flagArgumentDouble(kPitchFlag, 0)
if argData.isFlagSet(kRadiusFlag):
radius = argData.flagArgumentDouble(kRadiusFlag, 0)
controlVertices = OpenMaya.MPointArray()
knotSequences = OpenMaya.MDoubleArray()
# Set up cvs and knots for the helix
#
for i in range(0, ncvs):
controlVertices.append( OpenMaya.MPoint( radius * math.cos(i),
pitch * i, radius * math.sin(i) ) )
for i in range(0, nknots):
knotSequences.append( i )
# Now create the curve
#
curveFn = OpenMaya.MFnNurbsCurve()
nullObj = OpenMaya.MObject()
try:
# This plugin normally creates the curve by passing in the
# cv's. A function to create curves by passing in the ep's
# has been added. Set this to False to get that behaviour.
#
if True:
curveFn.create( controlVertices,
knotSequences, deg,
OpenMaya.MFnNurbsCurve.kOpen,
0, 0,
nullObj )
else:
curveFn.createWithEditPoints(controlVertices,
3, OpenMaya.MFnNurbsCurve.kOpen,
False, False, False)
except:
sys.stderr.write( "Error creating curve.\n" )
raise
# Creator
def cmdCreator():
# Create the command
return OpenMayaMPx.asMPxPtr( scriptedCommand() )
# Syntax creator
def syntaxCreator():
syntax = OpenMaya.MSyntax()
syntax.addFlag(kPitchFlag, kPitchLongFlag, OpenMaya.MSyntax.kDouble)
syntax.addFlag(kRadiusFlag, kRadiusLongFlag, OpenMaya.MSyntax.kDouble)
return syntax
# Initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "1.0", "Any")
try:
mplugin.registerCommand( kPluginCmdName, cmdCreator, syntaxCreator )
except:
sys.stderr.write( "Failed to register command: %s\n" % kPluginCmdName )
raise
# Uninitialize the script plug-in
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterCommand( kPluginCmdName )
except:
sys.stderr.write( "Failed to unregister command: %s\n" % kPluginCmdName )
raise
This example includes the syntax creator function along with parsing operations in the doIt() method of the class.
The Maya Python API contains several methods that should only be called from the class the method belongs to. We follow the Python designation of using an _ as the first letter of the method name to indicate that protection applies to this method. Several examples of these methods exist in the MPxNode class:
_forceCache()
_setMPSafe()
Please respect the method usage requirements of protected methods in the Maya Python API.
Need self but only API object is available
When implementing a proxy class in Python, it is natural to define methods within the Python class for handling the getting and setting of state. For example, you wish to maintain a vector as a class variable. Within the Maya Python API, there are methods that are creators of classes that can exist outside of the class. Often, these methods return a pointer value. In the Maya Python API, these pointer values are not the same as the self of a Python class. As a result, there is no straight forward way to access the class variables defined on self. A workaround using a dictionary and a special function OpenMayaMPx.asHashable() makes this possible.
kTrackingDictionary = {}
def __init__(self):
OpenMayaMPx.MPxToolCommand.__init__(self)
self.setCommandString(kPluginCmdName)
self.__delta = OpenMaya.MVector()
kTrackingDictionary[OpenMayaMPx.asHashable(self)] = self
# Code is in a different class
# Pointer is returned
newCmd = self._newToolCommand()
# Use pointer to get to self of the command
self.__cmd = kTrackingDictionary.get(OpenMayaMPx.asHashable(newCmd), None)
# Set the class variable
self.__cmd.setVector(0.0, 0.0, 0.0)
def __del__(self):
del kTrackingDictionary[OpenMayaMPx.asHashable(self)]
For examples that demonstrate these principles, see the Development Kit.
There are some methods in the Maya Python API that require <iosteam> operating system types. As these are not included in Python, a MStreamUtils class is available for creating and using these type of objects. Please check the Development kit for examples on how to use this class.
Often when writing an MPx proxy class, the scripts will require calling into the parent class. This is done using notation such as the following:
We have provided a wrapper class MGLFunctionTable for using OpenGL functionality in script on all of our support platforms. To acquire a reference to this class use the following code:
glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer()
glFT = glRenderer.glFunctionTable()
Nested iterators and Garbage Collection
When writing nested iterators, it is normal to have the current item of the outer loop passed into an inner loop. For example, the current item of an MItSelectionList is passed into a MItSurfaceCV iterator. You may encounter garbage collection issues in this case where the inner iterator is holding on to information that may no longer be valid. You can work around this by resetting the inner iterator to None once its loop has been completed. This releases the iterator’s information before the outer loop continues, which is important if current items are being rebuilt or updated.
It is possible to write standalone scripts that make use of the wrapper classes and function sets to modify the Maya model. These scripts are run from the command line. A simple “hello world” standalone script follows:
import maya.standalone
import maya.OpenMaya as OpenMaya
import sys
def main( argv = None ):
try:
maya.standalone.initialize( name='python' )
except:
sys.stderr.write( "Failed in initialize standalone application" )
raise
sys.stderr.write( "Hello world! (script output)\n" )
OpenMaya.MGlobal().executeCommand( "print \"Hello world! (command script output)\\n\"" )
if __name__ == "__main__":
main()
After the standalone is initialized, function sets and wrapper classes can be used to create and modify a Maya model. This script must be run using the Python executable that is supplied with Maya. For example: