You are on page 1of 11

API Guide

Using the Maya Python API


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.

Importing modules
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

Help on a module or class


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: help(maya.OpenMaya.MVector) It is also possible to display the information of an entire module: help(maya.OpenMaya) This operation will take a while to return since the OpenMaya module is very large.

Writing scripts
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: MFn Any class with this prefix is a function set used to operate on MObjects of a particular type. MIt 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. MPx Classes with this prefix are all Proxies, that is, API classes designed for you to derive from and create your own object types.

M classes 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: To instantiate a class, use the fn = OpenMaya.MFnFunctionSet() notation. MObjects can be created using node = OpenMaya.MObject(). Although Python is a typeless language, you must instantiate the correct type in order to pass it as a parameter of the class. Python strings are passed and returned in place of the MString wrapper class. Note For the sake of clarity, the example above has omitted error checking.

Scripted plug-ins
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.

Using a scripted plug-in


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. Note Although, it is possible to have a non scripted plug-in .py script on the MAYA_PLUG_IN_PATH, these items will not load. Warnings will be issued that entry points cannot be found. 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: Import the Maya module so that the cmds module becomes available Invoke the custom command spHelix() Notes The sp prefix is used for scripted plug-in. Scripted plug-ins must be loaded using the loadPlugin command. It cannot be loaded by running the source of a scripted plug-in.

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


Writing a scripted plug-in requires the definition of some specialized functions within the plug-in. The scripted plug-in must: Define initializePlugin and uninitializePlugin entry points. Register and unregister the proxy class within these entry points. Implement creator and initialize methods (as required) which Maya calls to build the proxy class. Implement the required functionality of the proxy class. This requires importing the necessary modules. The following sections describe these pieces in more detail with examples. Importing 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 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. Class implementation 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 commands doIt() method simply prints out Hello World!. Initialization Functions 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. Error Conditions 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: 1. A call fails and the failure state needs to be preserved:
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. 2. A call fails and the failure state needs to be cleared: 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. Classes support slicing 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]

# Result:[2, 4, 6] # 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. Messages 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 using 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)

References to Basic Types 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 is used to create, get and set values of these types. Commands with Arguments 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: OpenMaya.MFnNurbsCurve.kOpen, except: # 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 sys.stderr.write( "Error creating curve.\n" ) raise False, False, False) curveFn.createWithEditPoints(controlVertices, 3,

This example includes the syntax creator function along with parsing operations in the doIt() method of the class. Protected methods 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. Follow these steps: 1. Declare a dictionary object.
kTrackingDictionary = {}

2. In the initialization method of the object of interest, use the OpenMayaMPx.asHashable() method to store self in the dictionary.

def __init__(self): OpenMayaMPx.MPxToolCommand.__init__(self) self.setCommandString(kPluginCmdName) self.__delta = OpenMaya.MVector() kTrackingDictionary[OpenMayaMPx.asHashable(self)] = self

3. Retrieve self from the dictionary using the pointer.


# 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)

4. Clean up the tracking dictionary.


def __del__(self): del kTrackingDictionary[OpenMayaMPx.asHashable(self)]

For examples that demonstrate these principles, see the Development Kit. Operating System Types 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. Calling into the Parent 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: matrix = OpenMayaMPx.MPxTransformationMatrix.asMatrix(self) Enum values Enum values are accessed using a moduleName.className.value notation such as: OpenMaya.MSyntax.kDouble Using OpenGL 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()

Standalone Scripts
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: $MAYA_LOCATION/bin/mayapy helloWorld.py