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:

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.

NoteAlthough, 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:

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:

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 command’s 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.

  1. 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:
				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.

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 = {}
  1. 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
  1. 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)
  1. 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()

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.

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