オブジェクトの作成と操作

コマンド プラグインのコンテキストでは、オブジェクトの作成と操作は Maya の元に戻す/やり直しシステムに密接に関連付けられています。実際には、ディペンデンシー グラフとそのノードの状態を操作するコマンドは(DAG を含みます)、「元に戻す」操作を実装してアクションを元に戻し、Maya の内部状態が「元に戻す」スタックと同期するようにする必要があります。このトピックでは、元に戻すことが可能なコマンド内でオブジェクトを作成して操作する方法を例示します。

元に戻すことが可能なコマンドのサンプル

最初に、元に戻すことが可能なコマンドの構造を示します。後で、カメラを作成して操作する、元に戻すことが可能なコマンドを示します。

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

'''

前述のコマンド プラグインには、コマンド実行プロセス中に Maya によって呼び出される 4 つの重要な関数が含まれます。

MDagModifier でオブジェクトを作成する

DAG ノードの作成および DAG ノードの操作を元に戻すことを容易にするため、Maya API には MDagModifier クラスが用意されています。このクラスの派生元は MDGModifier であり、ディペンデンシー グラフ ノードの作成と操作に使用されます。DAG はディペンデンシー グラフのサブセットなので、MDGModifier によって提供される関数は、MDagModifier よっても提供されます。これを覚えておくと、このトピックの MDagModifier に対して行われる関数呼び出しのクラス マニュアルを参照するときに役立つ場合があります。

次の元に戻すことができるコマンドでは、カメラを作成して操作し、この新しく作成したカメラに現在のビューを設定しています。

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

'''

上の例では、主要な関数は MDagModifer.createNode()MDagModifier.doIt()MDagModifier.undoIt() です。これらの関数は、それぞれコマンドの doIt() メソッド、redoIt() メソッド、undoIt() メソッドで使用されています。

  1. MDagModifier.createNode(): MDagModifier.createNode() が呼び出されると、操作が MDagModifier 内でキューに入れられて、MObject のインスタンスが返されます。この MObject は、DAG にまだ含まれていない、新しく作成された Maya DAG ノードへのハンドルです。1 番目のパラメータとして文字列を受け取る MDagModifier.createNode() のメソッド シグネチャを呼び出していることに注意してください。この文字列は、作成されたノード タイプの名前を示します。有効なノード タイプ名は、『Maya ユーザ ガイド』 > 「テクニカル ドキュメント」 > 「ノード」に列記されています。'transform''camera' などです。MDagModifier.renameNode() の呼び出しも MDagModifier 内でキューに入れられ、ノードの名前変更を実行する/元に戻すために使用されます。

        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(): MDagModifier.doIt() が呼び出されると、前にキューに入れられた操作が効果的に実行されます。キューに入れられた MDagModifier.createNode() 操作は、作成されたノードを DAG に追加することによって完了します。コマンドの redoIt() メソッドの MDagModifier.doIt() を呼び出すと、コマンドの doIt() メソッドで定義されている、作成されたノードの同じセットを操作できます。コマンドの 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.
            self.dagModifier.doIt()
    
            # ... object manipulations ...

    注: MDagModifier.doIt() は同じコマンドで何回でも呼び出すことができます。これはノードを切断して削除する必要がある場合に必要になります。詳細については、MDGModifier クラスのマニュアルを参照してください。

  3. MDagModifier.undoIt(): MDagModifier.undoIt() を呼び出すと、MDagModifier.doIt() の呼び出しで完了しているすべてのキューに入れられた操作が元に戻されます。

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

    注: コマンド内での MDagModifier.doIt() の呼び出し回数に関係なく、MDagModifier.undoIt() の呼び出しは 1 回だけで十分です。

オブジェクトの操作

このコマンドの redoIt() メソッドの本体では、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 )

        # ...

これにより、Maya API で MObject および関数セットを使用できるようになります。**

警告: MDagModifier を使用しないノードを作成、操作する

関数セットの多くでは、create() 関数(例: MFnCamera.create())も提供されており、これは自動的に適切なシェープとトランスフォーム ノードをインスタンス化します。**これらの関数は、シーンを初期化など、1 回だけ使用する、元に戻せないコマンド内でのみ使用することをお勧めします。このガイドラインの例外は MFnMesh.create() で、コマンド内での使用は「例: シーンの作成」で示されています。この例では、MDagModifier.createNode() で親トランスフォーム ノードを作成し、MFnMesh.create() を使用してその下のメッシュ データを子ノードとして定義する必要があります。

同様に、MFnDagNode 関数セットには、MFnDagNode.addChild()MFnDagNode.removeChild()、その他の DAG 階層操作関数が含まれています。**1 回だけ使用される元に戻せないコマンドを作成している場合や、コマンドの undoIt() ロジックを手動で実装してこれらのアクションを元に戻す場合以外は、MDagModifier.reparentNode() 関数を使用してから MDagModifier.doIt() を呼び出すことをお勧めします。

警告: MGlobal.deleteNode() でのノード削除

また、コマンドの undoIt() メソッドの中で MGlobal.deleteNode() を呼び出してコマンドのノード作成を元に戻すことがあります。これはお勧めできません。MGlobal.deleteNode() を呼び出すと、実際は MEL ノード削除コマンドが内部的に実行され、それによってコマンドを元に戻すプロセスの実行中に「元に戻す」スタックにコマンドが追加されるからです。これにより Maya の「元に戻す」スタック内に矛盾が発生し、クラッシュが発生することがあります。

注: 同じ理由で、コマンド プラグイン内で maya.cmds 関数を呼び出すことをお勧めしません。代わりの安全な方法としては、MDagModifier.commandToExecute() 関数を使用して MEL コマンドをキューに加えると、MDagModifier.doIt() が呼び出されたときに安全に実行されます。同じ MEL コマンドは MDagModifier.undoIt() を呼び出すと安全に元に戻るので、Maya 内の「元に戻す」スタックの不整合を避けることができます。詳細は、「例: IK ジョイント チェーンを作成する」を参照してください。