API Guide > Maya Python Plug-in Learning Path > Command Plug-in Basics >

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

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

- isUndoable() - If the command's isUndoable() function returns True, the instance of that command will be added to Maya's undo stack.

          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

- doIt() - The doIt() function of each command instance is invoked once when the command is first executed. To maximize code re-use, it is recommended to have the doIt() function instantiate all the objects and variables your command instance will require, and to subsequently call redoIt() from within doIt() function body.

          def doIt(self, args):
              ''' doIt() is called once when the command is first executed. '''

              # Parse any arguments when doIt() is invoked.
              self.parseArgs( args )

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

- redoIt() - The redoIt() function is invoked by Maya every time the command is re-done. redoIt() should therefore be used manipulate the state of the objects used in your command. Keep in mind that this state manipulation should be reversed in the undoIt() function.

          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

- undoIt() - The undoIt() function is called when the command is undone. The objective of the undoIt() function is to ensure that each object manipulated by your command has its state reversed to before your command was executed. If the instance of the command does not reverse its manipulations correctly, Maya's internal state will be inconsistent in regards to its undo/redo stack.

          def undoIt(self):
              ''' undoIt() is called every time the instance of this command is undone. '''

              # ... Reverse any object creation or manipulations here ...
              pass

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

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

   NoteIt 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.

- Maya Objects (MObject) - The Maya API provides access to Maya objects as instances of the MObject wrapper class. Instances of MObject can therefore represent cameras, lights, meshes, transforms, and dependency graph nodes. The internal state of an MObject is manipulated via a compatible function set (classes derived from MFnBase). All instances of MObject have one and only one Maya Object type (MObject.apiType()), for example: MFn.kTransform, or MFn.kCamera, which may be compatible with multiple types of function sets. The MObject.hasFn() method determines if an instance of MObject is compatible with a given function set.

- Function Sets (MFnBase and subclasses) - Function sets are provided in the Maya API to manipulate the state of compatible MObjects. They exist as singletons, meaning that Maya contains only one function set of each type internally. A function set can only manipulate one MObject at a time, though this object can be changed using MFnBase.setObject(). A reference to a specific function set is obtained via its constructor, which can accept an MObject to set the current working object.

  An MObject's function set compatibility follows the class hierarchy up to the MFnBase class. An instance of MObject with a Maya Object type of MFn.kCamera is therefore compatible with the following function sets: MFnCamera, MFnDagNode, MFnDependencyNode, and MFnBase

  The code sample and diagram below illustrate the use of the MFnTransform function set to set the translation of a compatible MObject (of type Mfn.kTransform), namely the transform node we had previously assigned to self.cameraTransformObj and named as 'myCameraTransform'.

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

  NoteThe second parameter (OpenMaya.MSpace.kTransform) in the call to MFnTransform.setTranslation() specifies that the translation should be applied in the transform node's local coordinate space.

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

NoteFor 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.