コマンド プラグインのコンテキストでは、オブジェクトの作成と操作は Maya の元に戻す/やり直しシステムに密接に関連付けられています。実際には、ディペンデンシー グラフとそのノードの状態を操作するコマンドは(DAG を含みます)、「元に戻す」操作を実装してアクションを元に戻し、Maya の内部状態が「元に戻す」スタックと同期するようにする必要があります。このトピックでは、元に戻すことが可能なコマンド内でオブジェクトを作成して操作する方法を例示します。
最初に、元に戻すことが可能なコマンドの構造を示します。後で、カメラを作成して操作する、元に戻すことが可能なコマンドを示します。
# 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() '''
# 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 つの重要な関数が含まれます。
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
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()
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
DAG ノードの作成および DAG ノードの操作を元に戻すことを容易にするため、Maya API には MDagModifier クラスが用意されています。このクラスの派生元は MDGModifier であり、ディペンデンシー グラフ ノードの作成と操作に使用されます。DAG はディペンデンシー グラフのサブセットなので、MDGModifier によって提供される関数は、MDagModifier によっても提供されます。これを覚えておくと、このトピックの MDagModifier に対して行われる関数呼び出しのクラス マニュアルを参照するときに役立つ場合があります。
次の元に戻すことができるコマンドでは、カメラを作成して操作し、この新しく作成したカメラに現在のビューを設定しています。
# 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() '''
# 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() メソッドで使用されています。
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. self.dagModifier.doIt() # ... object manipulations ...
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()
このコマンドの 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 および関数セットの使用を導入しています。
Maya オブジェクト(MObject): Maya API は、Maya オブジェクトへのアクセスを MObject ラッパ クラスのインスタンスとして提供します。したがって、MObject のインスタンスは、カメラ、ライト、メッシュ、変換、およびディペンデンシー グラフ ノードを表すことができます。MObject の内部状態は、互換性のある関数セット(MFnBase から派生されるクラス)を使用して操作します。MObject のすべてのインスタンスは、ただ 1 つの Maya オブジェクト タイプ(MObject.apiType())を持つか(例: MFn.kTransform)、または関数セットの複数のタイプと互換性がある場合がある MFn.kCamera を持ちます。MObject.hasFn() メソッドは、MObject のインスタンスと特定の関数セットとの間に互換性があるかどうかを判定します。
関数セット(MFnBase およびサブクラス): 関数セットは、互換性のある MObject の状態を操作するために Maya API で提供されています。シングルトンとして存在します。つまり、Maya の内部には各タイプの関数セットが 1 つだけ含まれます。関数セットは一度に 1 つの MObject だけを操作できますが、このオブジェクトは MFnBase.setObject() を使用して変更できます。特定の関数セットのリファレンスは、そのコンストラクタを使用して取得します。コンストラクタは、MObject を受け入れて現在の作業オブジェクトを設定できます。
MObject の関数セットの互換性は、MFnBase クラスまでのクラス階層に従います。したがって、MFn.kCamera の Maya オブジェクト タイプを持つ MObject のインスタンスは、MFnCamera、MFnDagNode、MFnDependencyNode、MFnBase との互換性があります。
次のコード サンプルと図は、互換性のある MObject (Mfn.kTransform タイプ)の移動を設定するための MFnTransform 関数セットの使用を示しています。つまり、前に self.cameraTransformObj に割り当てて '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 )
関数セットの多くでは、create() 関数(例: MFnCamera.create())も提供されており、これは自動的に適切なシェープとトランスフォーム ノードをインスタンス化します。これらの関数は、シーンを初期化など、1 回だけ使用する、元に戻せないコマンド内でのみ使用することをお勧めします。このガイドラインの例外は MFnMesh.create() で、コマンドでのその使用は例: シーンの作成で示されています。この例では、MDagModifier.createNode() で親トランスフォーム ノードを作成し、MFnMesh.create() を使用してその下の子ノードとしてメッシュ データを定義する必要があります。
同様に、MFnDagNode 関数セットには、MFnDagNode.addChild()、MFnDagNode.removeChild()、その他の DAG 回想操作関数が含まれます。1 回使用の元に戻せないコマンドを作成していない場合、またはコマンドの undoIt() ロジックを手動で実装してこれらのアクションを元に戻す場合は、MDagModifier.reparentNode() 関数を使用してから MDagModifier.doIt() を呼び出すことをお勧めします。
また、コマンドの undoIt() メソッドの中で MGlobal.deleteNode() を呼び出してコマンドのノード作成を元に戻すことがあります。これはお勧めできません。MGlobal.deleteNode() を呼び出すと、実際は MEL ノード削除コマンドが内部的に実行され、それによってコマンドを元に戻すプロセスの間に「元に戻す」スタックにコマンドが追加されるからです。これにより Maya の「元に戻す」スタック内に矛盾が発生し、クラッシュが発生することがあります。