Creating and Manipulating Objects

In the context of command plug-ins, object creation and manipulation is intimately tied to Maya's undo/redo system. In fact, commands which manipulate the state of the Dependency Graph and its nodes (which includes the DAG) should implement an undo operation to reverse the action to ensure that Maya's internal state remains in sync with its undo stack. In this topic, we illustrate how to create and manipulate objects within an undoable command.

Sample Undoable Command

We begin by presenting the structure of an undoable command. Later in this topic, we will present an undoable command which creates and manipulates a camera.

Python API 2.0:
# pySampleUndoableCommand.py

import sys
import maya.api.OpenMaya as OpenMaya
# ... additional imports here ...

def maya_useNewAPI():
	"""
	The presence of this function tells Maya that the plugin produces, and
	expects to be passed, objects created using the Maya Python API 2.0.
	"""
	pass

kPluginCmdName = 'myUndoableCommandName'


##########################################################
# Plug-in 
##########################################################
class MyUndoableCommand( OpenMaya.MPxCommand ):
    
    def __init__(self):
        ''' Constructor. '''
        OpenMaya.MPxCommand.__init__(self)
    
    
    def doIt(self, args):
        ''' doIt() is called once when the command is first executed. '''
        
        # ... Perform any object creation here, since doIt() is only called once per command instance ...
        
        # Call self.redoIt() to perform the command's actual work. This function call flow
        # is useful for code re-use.
        self.redoIt()
        
        
    def redoIt(self):
        ''' redoIt() is called every time the instance of this command is re-done from
        the undo queue. '''
        
        # ... Perform any object manipulation here ...
        pass
    
    
    def undoIt(self):
        ''' undoIt() is called every time the instance of this command is undone. '''
        
        # ... Reverse any object creation or manipulations here ...
        pass
    
    
    def isUndoable(self):
        ''' This function indicates whether or not the command is undoable. If the
        command is undoable, each executed instance of that command is added to the
        undo queue. '''
        
        # We must return True to specify that this command is undoable.
        return True
    
    
##########################################################
# Plug-in initialization.
##########################################################
def cmdCreator():
    ''' Create an instance of our command. '''
    return MyUndoableCommand() 

def initializePlugin( mobject ):
    ''' Initialize the plug-in when Maya loads it. '''
    mplugin = OpenMaya.MFnPlugin( mobject )
    try:
        mplugin.registerCommand( kPluginCmdName, cmdCreator )
    except:
        sys.stderr.write( 'Failed to register command: ' + kPluginCmdName )

def uninitializePlugin( mobject ):
    ''' Uninitialize the plug-in when Maya un-loads it. '''
    mplugin = OpenMaya.MFnPlugin( mobject )
    try:
        mplugin.deregisterCommand( kPluginCmdName )
    except:
        sys.stderr.write( 'Failed to unregister command: ' + kPluginCmdName )

##########################################################
# Sample usage.
##########################################################
''' 
# Copy the following lines and run them in Maya's Python Script Editor:

import maya.cmds as cmds
cmds.loadPlugin( 'pySampleUndoableCommand.py' )
cmds.myUndoableCommandName()

'''

Python API 1.0:
# sampleUndoableCommand.py

import sys
import maya.OpenMayaMPx as OpenMayaMPx
# ... additional imports here ...

kPluginCmdName = 'myUndoableCommandName'

##########################################################
# Plug-in 
##########################################################
class MyUndoableCommand( OpenMayaMPx.MPxCommand ):
    
    def __init__(self):
        ''' Constructor. '''
        OpenMayaMPx.MPxCommand.__init__(self)
    
    
    def doIt(self, args):
        ''' doIt() is called once when the command is first executed. '''
        
        # ... Perform any object creation here, since doIt() is only called once per command instance ...
        
        # Call self.redoIt() to perform the command's actual work. This function call flow
        # is useful for code re-use.
        self.redoIt()
        
        
    def redoIt(self):
        ''' redoIt() is called every time the instance of this command is re-done from
        the undo queue. '''
        
        # ... Perform any object manipulation here ...
        pass
    
    
    def undoIt(self):
        ''' undoIt() is called every time the instance of this command is undone. '''
        
        # ... Reverse any object creation or manipulations here ...
        pass
    
    
    def isUndoable(self):
        ''' This function indicates whether or not the command is undoable. If the
        command is undoable, each executed instance of that command is added to the
        undo queue. '''
        
        # We must return True to specify that this command is undoable.
        return True
    
    
##########################################################
# Plug-in initialization.
##########################################################
def cmdCreator():
    ''' Create an instance of our command. '''
    return OpenMayaMPx.asMPxPtr( MyUndoableCommand() )

def initializePlugin( mobject ):
    ''' Initialize the plug-in when Maya loads it. '''
    mplugin = OpenMayaMPx.MFnPlugin( mobject )
    try:
        mplugin.registerCommand( kPluginCmdName, cmdCreator )
    except:
        sys.stderr.write( 'Failed to register command: ' + kPluginCmdName )

def uninitializePlugin( mobject ):
    ''' Uninitialize the plug-in when Maya un-loads it. '''
    mplugin = OpenMayaMPx.MFnPlugin( mobject )
    try:
        mplugin.deregisterCommand( kPluginCmdName )
    except:
        sys.stderr.write( 'Failed to unregister command: ' + kPluginCmdName )

##########################################################
# Sample usage.
##########################################################
''' 
# Copy the following lines and run them in Maya's Python Script Editor:

import maya.cmds as cmds
cmds.loadPlugin( 'sampleUndoableCommand.py' )
cmds.myUndoableCommandName()

'''

The command plug-in above contains four important functions which are called by Maya during the command execution process:

Creating Objects with MDagModifier

To facilitate the creation of DAG nodes, and the undoing of DAG node manipulations, the Maya API provides the MDagModifier class. This class is derived from MDGModifier, which is used to create and manipulate Dependency Graph nodes. Recall that the DAG is a subset of the Dependency Graph, and so the functions provided by MDGModifier are also provided by MDagModifier - this may help you when consulting the class documentation of the function calls made to MDagModifier in this topic.

In the undoable command that follows, we create and manipulate a camera, and also set the current view to this newly created camera:

Python API 2.0:
# pySampleCameraCommand.py

import sys
import maya.api.OpenMaya as OpenMaya
import maya.api.OpenMayaUI as OpenMayaUI

def maya_useNewAPI():
	"""
	The presence of this function tells Maya that the plugin produces, and
	expects to be passed, objects created using the Maya Python API 2.0.
	"""
	pass
	
kPluginCmdName = 'myCameraCommand'

##########################################################
# Plug-in 
##########################################################
class MyCameraCommand( OpenMaya.MPxCommand ):
    
    def __init__(self):
        ''' Constructor. '''
        OpenMaya.MPxCommand.__init__(self)
    
    
    def doIt(self, args):
        ''' doIt() is called once when the command is first executed. ''' 
        
        # This MDagModifier object will allow us to undo and redo the creation of DAG nodes in our command.
        self.dagModifier = OpenMaya.MDagModifier()
        
        # Create the camera transform node.
        self.cameraTransformObj = self.dagModifier.createNode( 'transform' )
        self.dagModifier.renameNode( self.cameraTransformObj, 'myCameraTransform' )
        
        # Create the camera shape node as a child of the camera transform node.
        self.cameraShapeObj = self.dagModifier.createNode( 'camera', self.cameraTransformObj )
        self.dagModifier.renameNode( self.cameraShapeObj, 'myCameraShape' )
        
        # Call self.redoIt() to perform the command's actual work. This function call flow
        # is useful for code re-use.
        self.redoIt()
        
        
    def redoIt(self):
        ''' redoIt() is called every time the instance of this command is re-done from
        the undo queue. '''
        
        # Perform the operations enqueued within our reference to MDagModifier. This effectively
        # creates the DAG nodes specified using self.dagModifier.createNode().
        self.dagModifier.doIt()
        
        # Set the translation value of the camera's transform node. 
        transformFn = OpenMaya.MFnTransform( self.cameraTransformObj )
        transformFn.setTranslation( OpenMaya.MVector( 0, 5, 30 ), OpenMaya.MSpace.kTransform )
        
        # Store the previous camera before we switch to the camera created within this command.
        # In undo() we will revert to this previous camera.
        self.previousCamera = OpenMaya.MDagPath()
        currentView = OpenMayaUI.M3dView.active3dView()
        self.previousCamera=currentView.getCamera() # self.previousCamera is now populated with the current camera before we switch.
        
        # Get the DAG path of our camera shape node.
        cameraDagPath = OpenMaya.MDagPath()
        dagNodeFn = OpenMaya.MFnDagNode( self.cameraShapeObj )
        cameraDagPath = dagNodeFn.getPath()
        
        # Set the camera view to the one we switched
        currentView.setCamera( cameraDagPath )
    
    
    def undoIt(self):
        ''' undoIt() is called every time the instance of this command is undone. '''
        
        # Switch back to the previous camera. We do not have to reverse the translation of 
        # self.cameraTransformObj because it will be excluded from the DAG once 
        # self.dagModifier.undoIt() is called below.
        currentView = OpenMayaUI.M3dView.active3dView()
        self.previousCamera = currentView.getCamera()
        
        # This call to MDagModifier.undoIt() undoes all the operations within the MDagModifier.
        self.dagModifier.undoIt()
    
    
    def isUndoable(self):
        ''' This function indicates whether or not the command is undoable. If the
        command is undoable, each executed instance of that command is added to the
        undo queue. '''
        
        # We must return True to specify that this command is undoable.
        return True
    
    
##########################################################
# Plug-in initialization.
##########################################################
def cmdCreator():
    ''' Create an instance of our command. '''
    return  MyCameraCommand() 

def initializePlugin( mobject ):
    ''' Initialize the plug-in when Maya loads it. '''
    mplugin = OpenMaya.MFnPlugin( mobject )
    try:
        mplugin.registerCommand( kPluginCmdName, cmdCreator )
    except:
        sys.stderr.write( 'Failed to register command: ' + kPluginCmdName )

def uninitializePlugin( mobject ):
    ''' Uninitialize the plug-in when Maya un-loads it. '''
    mplugin = OpenMaya.MFnPlugin( mobject )
    try:
        mplugin.deregisterCommand( kPluginCmdName )
    except:
        sys.stderr.write( 'Failed to unregister command: ' + kPluginCmdName )

##########################################################
# Sample usage.
##########################################################
''' 
# Copy the following lines and run them in Maya's Python Script Editor:

import maya.cmds as cmds
cmds.loadPlugin( 'pySampleCameraCommand.py' )
cmds.myCameraCommand()
cmds.undo()
cmds.redo()

'''
Python API 1.0:
# sampleCameraCommand.py

import sys
import maya.OpenMayaMPx as OpenMayaMPx
import maya.OpenMaya as OpenMaya
import maya.OpenMayaUI as OpenMayaUI

kPluginCmdName = 'myCameraCommand'

##########################################################
# Plug-in 
##########################################################
class MyCameraCommand( OpenMayaMPx.MPxCommand ):
    
    def __init__(self):
        ''' Constructor. '''
        OpenMayaMPx.MPxCommand.__init__(self)
    
    
    def doIt(self, args):
        ''' doIt() is called once when the command is first executed. ''' 
        
        # This MDagModifier object will allow us to undo and redo the creation of DAG nodes in our command.
        self.dagModifier = OpenMaya.MDagModifier()
        
        # Create the camera transform node.
        self.cameraTransformObj = self.dagModifier.createNode( 'transform' )
        self.dagModifier.renameNode( self.cameraTransformObj, 'myCameraTransform' )
        
        # Create the camera shape node as a child of the camera transform node.
        self.cameraShapeObj = self.dagModifier.createNode( 'camera', self.cameraTransformObj )
        self.dagModifier.renameNode( self.cameraShapeObj, 'myCameraShape' )
        
        # Call self.redoIt() to perform the command's actual work. This function call flow
        # is useful for code re-use.
        self.redoIt()
        
        
    def redoIt(self):
        ''' redoIt() is called every time the instance of this command is re-done from
        the undo queue. '''
        
        # Perform the operations enqueued within our reference to MDagModifier. This effectively
        # creates the DAG nodes specified using self.dagModifier.createNode().
        self.dagModifier.doIt()
        
        # Set the translation value of the camera's transform node. 
        transformFn = OpenMaya.MFnTransform( self.cameraTransformObj )
        transformFn.setTranslation( OpenMaya.MVector( 0, 5, 30 ), OpenMaya.MSpace.kTransform )
        
        # Store the previous camera before we switch to the camera created within this command.
        # In undo() we will revert to this previous camera.
        self.previousCamera = OpenMaya.MDagPath()
        currentView = OpenMayaUI.M3dView.active3dView()
        currentView.getCamera( self.previousCamera ) # self.previousCamera is now populated with the current camera before we switch.
        
        # Get the DAG path of our camera shape node.
        cameraDagPath = OpenMaya.MDagPath()
        dagNodeFn = OpenMaya.MFnDagNode( self.cameraShapeObj )
        dagNodeFn.getPath( cameraDagPath )
        
        # Set the camera view to the one we switched
        currentView.setCamera( cameraDagPath )
    
    
    def undoIt(self):
        ''' undoIt() is called every time the instance of this command is undone. '''
        
        # Switch back to the previous camera. We do not have to reverse the translation of 
        # self.cameraTransformObj because it will be excluded from the DAG once 
        # self.dagModifier.undoIt() is called below.
        currentView = OpenMayaUI.M3dView.active3dView()
        currentView.setCamera( self.previousCamera )
        
        # This call to MDagModifier.undoIt() undoes all the operations within the MDagModifier.
        self.dagModifier.undoIt()
    
    
    def isUndoable(self):
        ''' This function indicates whether or not the command is undoable. If the
        command is undoable, each executed instance of that command is added to the
        undo queue. '''
        
        # We must return True to specify that this command is undoable.
        return True
    
    
##########################################################
# Plug-in initialization.
##########################################################
def cmdCreator():
    ''' Create an instance of our command. '''
    return OpenMayaMPx.asMPxPtr( MyCameraCommand() )

def initializePlugin( mobject ):
    ''' Initialize the plug-in when Maya loads it. '''
    mplugin = OpenMayaMPx.MFnPlugin( mobject )
    try:
        mplugin.registerCommand( kPluginCmdName, cmdCreator )
    except:
        sys.stderr.write( 'Failed to register command: ' + kPluginCmdName )

def uninitializePlugin( mobject ):
    ''' Uninitialize the plug-in when Maya un-loads it. '''
    mplugin = OpenMayaMPx.MFnPlugin( mobject )
    try:
        mplugin.deregisterCommand( kPluginCmdName )
    except:
        sys.stderr.write( 'Failed to unregister command: ' + kPluginCmdName )

##########################################################
# Sample usage.
##########################################################
''' 
# Copy the following lines and run them in Maya's Python Script Editor:

import maya.cmds as cmds
cmds.loadPlugin( 'sampleCameraCommand.py' )
cmds.myCameraCommand()
cmds.undo()
cmds.redo()

'''

In the sample above, the key functions we are interested in (for now) are: MDagModifer.createNode(), MDagModifier.doIt() and MDagModifier.undoIt(). These functions are respectively distributed among our command's doIt(), redoIt() and undoIt() methods:

  1. MDagModifier.createNode() - When MDagModifier.createNode() is called, the operation is queued within the MDagModifier, and an instance of MObject is returned. This MObject is a handle to a newly created Maya DAG node which has not yet been included into DAG. Observe that we are invoking the method signature of MDagModifier.createNode() which accepts a string as the first parameter. This string represents the created node type's name. Valid node type names are listed under Maya User Guide > Technical Documentation > Nodes, for example: 'transform' and 'camera'. The call to MDagModifier.renameNode() is also queued within the MDagModifier, and is used to do/undo the renaming of a node.
        def doIt(self, args):
            ''' doIt() is called once when the command is first executed. ''' 
            
            # This MDagModifier object will allow us to undo and redo the creation of DAG nodes in our command.
            self.dagModifier = OpenMaya.MDagModifier()
            
            # Create the camera transform node.
            self.cameraTransformObj = self.dagModifier.createNode( 'transform' )
            self.dagModifier.renameNode( self.cameraTransformObj, 'myCameraTransform' )
            
            # Create the camera shape node as a child of the camera transform node.
            self.cameraShapeObj = self.dagModifier.createNode( 'camera', self.cameraTransformObj )
            self.dagModifier.renameNode( self.cameraShapeObj, 'myCameraShape' )
            
            # Call self.redoIt() to perform the command's actual work. This function call flow
            # is useful for code re-use.
            self.redoIt()
  2. MDagModifier.doIt() - When MDagModifier.doIt() is called, the previously queued operations are effectively executed. As such, the queued MDagModifier.createNode() operation will complete by adding the created node into the DAG. The call to MDagModifier.doIt() in our command's redoIt() method effectively lets us operate on the same set of created nodes defined in our command's doIt() method. Observe that we are not creating new nodes repeatedly when our command's redoIt() method is invoked.
        def redoIt(self):
            ''' redoIt() is called every time the instance of this command is re-done from
            the undo queue. '''
            
            # Perform the operations enqueued within our reference to MDagModifier.
            self.dagModifier.doIt()
            
            # ... object manipulations ...
    NOTE:MDagModifier.doIt() can be called several times in the same command. This is necessary when a node must be disconnected and deleted. Consult the MDGModifier class documentation for more information.
  3. MDagModifier.undoIt() - When MDagModifier.undoIt() is called, all the queued operations which have been completed in calls to MDagModifier.doIt() are undone.
        def undoIt(self):
            ''' undoIt() is called every time the instance of this command is undone. '''
            
            # ... revert object manipulations ...
            
            # This call to MDagModifier.undoIt() undoes all the operations queued within the MDagModifier.
            self.dagModifier.undoIt()
    NOTE:It is sufficient to call MDagModifier.undoIt() only once, despite the number of calls to MDagModifier.doIt() within the command.

Manipulating Objects

In our command's redoIt() method body, we change the position of our camera's transform node via a call to MFnCamera.setTranslation():

    def redoIt(self):
        ''' redoIt() is called every time the instance of this command is re-done from
        the undo queue. '''
        
        # ...
        
        # Set the translation value of the camera's transform node. 
        transformFn = OpenMaya.MFnTransform( self.cameraTransformObj )
        transformFn.setTranslation( OpenMaya.MVector( 0, 5, 30 ), OpenMaya.MSpace.kTransform )
        
        # ...

This introduces the use of MObjects and function sets in the Maya API.

Warning: Creating and Manipulating Nodes Without MDagModifier

You will notice that the majority of function sets also provide a create() function, for example: MFnCamera.create(), which takes care of instantiating an appropriate shape and transform node for you. We recommend only using these functions within one-time-use, non-undoable commands, for example to initialize a scene. An exception to this guideline is MFnMesh.create(), whose use in a command is illustrated in Example: Creating a Scene. In this example, we create a parent transform node with MDagModifier.createNode(), and must use MFnMesh.create() to define the mesh data under it as a child node.

In a similar fashion, you may notice that the MFnDagNode function set contains MFnDagNode.addChild(), MFnDagNode.removeChild(), and other DAG hierarchy manipulation functions. Unless you are creating a single-use, non-undoable command, or want to manually implement your command's undoIt() logic to reverse these actions, it is preferable to use the MDagModifier.reparentNode() function, followed by a call to MDagModifier.doIt().

Warning: Node Deletion with MGlobal.deleteNode()

You may also be tempted to reverse your command's node creations via calls to MGlobal.deleteNode() within your command's undoIt() method. Doing so is not advisable, as calling MGlobal.deleteNode() actually executes the MEL node deletion command internally, thereby causing Maya to add a command to the undo stack while it is in the process of undoing a command. This causes an inconsistency within Maya's undo stack, and can result in a crash.

NOTE:For the same reason, we do not recommend invoking maya.cmds functions within a command plug-in. The safe alternative is to use the MDagModifier.commandToExecute() function to enqueue MEL commands which will be safely executed when MDagModifier.doIt() is called. The same MEL commands will be safely undone when MDagModifier.undoIt() is called, thus avoiding any undo stack inconsistencies within Maya. Consult Example: Creating an IK Joint Chain for more information.