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()
    NOTE:
    • 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 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.

  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.

Exceptions versus MStatus

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:

  • if the status is MS::kUnknownParameter, then the string unknown is returned to Python;
  • if the status is MS::kSuccess, then nothing is returned and no exception is raised;
  • if the status is anything else, then nothing is returned but a RuntimeError exception is raised.

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:

  • if the status is MS::kSuccess, then no exception is raised;
  • if the status is anything else, then nothing is returned but a RuntimeError exception is raised.

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.

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

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 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 in the API Reference documentation.

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(OpenMaya.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)

References to enumerated types should be treated as references to short ints.

Commands with Flags

A command which has flags must specify its syntax using an MSyntax object and parse the flags using the MArgParser or MArgDatabase classes, otherwise the flags will not be accessible when the command is used from within Python. This is true regardless of whether the command is written in Python or C++. Below is an example of a scripted plug-in which implements a command with flags:

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.

Getting derived object from MPx base object

Some API methods return your custom objects as instances of their base MPx class rather than instances of your derived class. For example, MPxContext._newToolCommand() returns your custom tool command as an MPxToolCommand object and MPxSurfaceShapeUI.surfaceShape() returns your custom surface shape as an MPxSurfaceShape object. While these base instances refer to the same internal Maya objects as the original derived instances, they are separate Python objects which do not retain any of the member variable values set in the derived instances, so calling methods or accessing member variables on the returned objects may give incorrect results.

You can work around this problem by taking advantage of the properties of the OpenMayaMPx.asHashable() function. OpenMayaMPx.asHashable() takes an MPx object and returns a hash value which uniquely identifies the underlying Maya object. It doesn't matter whether the object is an instance of the base MPx class or your derived class: so long as they both refer to the same underlying Maya object they will return the same hash value. This allows you to set up a dictionary which can translate a base instance into its corresponding derived instance.

The first step is to add an empty dictionary to the class:

class MoveToolCmd(OpenMayaMPx.MPxToolCommand):
    kTrackingDictionary = {}

In the class's __init__ method, add an entry to the dictionary, mapping the new object's hash value to the object itself:

def __init__(self): 
    OpenMayaMPx.MPxToolCommand.__init__(self)
    MoveToolCmd.kTrackingDictionary[OpenMayaMPx.asHashable(self)] = self 

In the class's __del__ method, remove the deleted object from the dictionary:

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

Now that you have the tracking dictionary, you can use it to retrieve the derived object from its corresponding base object:

class MoveContext(OpenMayaMPx.MPxSelectionContext): 
    ... 
    
    def doPress(self, event): 

        # Create an instance of the move tool command. 
        baseCmd = self._newToolCommand() 
        derivedCmd = MoveToolCmd.kTrackingDictionary.get( 
                        OpenMayaMPx.asHashable(baseCmd), None 
                     ) 

        # Call its methods.
        derivedCmd.setVector(0.0, 0.0, 0.0) 

When defining custom node types (MPxNode, MPxSurfaceShape, etc) some additional effort is required. When Maya does a file open or new file operation it destroys the scene without executing the __del__ method for each node. That means that the entries for those nodes are never removed from the tracking dictionary. To get around this, callbacks must be set up in your initializePlugin method to clear their tracking dictionaries on file open and file new:

fileOpenCB = 0
fileNewCB = 0

def clearTrackingDictionaries(unused):
    MyNode.kTrackingDictionary = {} 
    MySurfaceShape.kTrackingDictionary = {}

def initializePlugin(plugin): 
    ... 
    fileOpenCB = OpenMaya.MSceneMessage.addCallback(
                     OpenMaya.MSceneMessage.kBeforeOpen, 
                     clearTrackingDictionaries 
                 )
    fileNewCB  = OpenMaya.MSceneMessage.addCallback(
                     OpenMaya.MSceneMessage.kBeforeNew, 
                     clearTrackingDictionaries 
                 )
    ...
}

def uninitializePlugin(plugin): 
    ... 
    if fileOpenCB != 0:
        OpenMaya.MSceneMessage.removeCallback(fileOpenCB) 
        fileOpenCB = 0 

    if fileNewCB != 0: 
        OpenMaya.MSceneMessage.removeCallback(fileNewCB)
        fileNewCB = 0

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 Developer 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\"" )
    maya.standalone.uninitialize()
    
if __name__ == "__main__":
    main()

After the standalone is initialized, Maya commands and API classes can be used to create and modify a Maya model. When finished, the standalone is uninitialized to allow Maya to clean up after itself. Do not attempt to use Maya commands or API classes before the call to initialize() or after the call to uninitialize(); otherwise, unstable behaviour may result.

A standalone script must be run using the Python executable that is supplied with Maya. For example:

$MAYA_LOCATION/bin/mayapy helloWorld.py