コマンド プラグインのコンテキストでは、オブジェクトの作成と操作は 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 つの重要な関数が含まれます。
isUndoable(): コマンドの isUndoable()
関数が True
を返す場合、そのコマンドのインスタンスは Maya の「元に戻す」スタックに追加されます。
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(): 各コマンド インスタンスの doIt()
関数は、コマンドが最初に実行されるときに 1 回呼び出されます。コードを最大限に再利用するには、コマンド インスタンスで必要なすべてのオブジェクトと変数を doIt()
関数でインスタンス化し、その後で doIt()
関数本体内から redoIt()
を呼び出すことをお勧めします。
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(): redoIt()
関数は、コマンドが再実行されるたびに Maya によって呼び出されます。したがって、コマンドで使用されているオブジェクトの状態を操作するには、redoIt()
を使用する必要があります。この状態操作は、undoIt()
関数で元に戻す必要があることに留意してください。
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(): undoIt()
関数は、コマンドが元に戻されると呼び出されます。undoIt()
関数の目的は、コマンドによって操作される各オブジェクトが、コマンドの実行前に戻った状態を保持することです。コマンドのインスタンスが操作を正しく元に戻さない場合、Maya の内部状態が、元に戻す/やり直しスタックと矛盾します。
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
に対して行われる関数呼び出しのクラス マニュアルを参照するときに役立つ場合があります。
次の元に戻すことができるコマンドでは、カメラを作成して操作し、この新しく作成したカメラに現在のビューを設定しています。
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()
メソッドで使用されています。
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()
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
クラスのマニュアルを参照してください。
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
および関数セットを使用できるようになります。**
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 )
注:
MFnTransform.setTranslation()
の呼び出しの 2 番目のパラメータ(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 の「元に戻す」スタック内に矛盾が発生し、クラッシュが発生することがあります。
注: 同じ理由で、コマンド プラグイン内で
maya.cmds
関数を呼び出すことをお勧めしません。代わりの安全な方法としては、MDagModifier.commandToExecute()
関数を使用して MEL コマンドをキューに加えると、MDagModifier.doIt()
が呼び出されたときに安全に実行されます。同じ MEL コマンドはMDagModifier.undoIt()
を呼び出すと安全に元に戻るので、Maya 内の「元に戻す」スタックの不整合を避けることができます。詳細は、「例: IK ジョイント チェーンを作成する」を参照してください。