ディペンデンシー グラフ プラグイン

次のトピックでは、ディペンデンシー グラフ プラグインの基本的なコード構造を示します。

ディペンデンシー グラフ プラグインのサンプル

次の Python コードは、ディペンデンシー グラフ プラグインのサンプルです。このサンプルは、myNodeName というユーティリティ(utility/general)ハイパーシェード ノードを定義します。このノードには、浮動小数点数(十進数)を受け取る 1 つの入力アトリビュートと入力された浮動小数点数を出力する 1 つの出力アトリビュートがあります。このノードは、cmds.createNode( 'myNodeName' )または cmds.shadingNode( 'myNodeName', asUtility=True )を使用して作成されます。このノードが後者のコマンドで作成された場合、または Maya ハイパーシェード ユーザ インタフェース(テキスト ボックスに「My Node Name」と入力してフィルタ)を介して手動で作成された場合は、ノードのインスタンスがウィンドウ > レンダリング エディタ > ハイパーシェード > 'ユーティリティ' タブ(Window > Rendering Editors > Hypershade > 'Utilities' Tab)に表示されます。

Python API 2.0:
# pySampleDGNode.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

# Plug-in information:
kPluginNodeName = 'myNodeName'              # The name of the node.
kPluginNodeClassify = 'utility/general'     # Where this node will be found in the Maya UI.
kPluginNodeId = OpenMaya.MTypeId( 0x87EFE ) # A unique ID associated to this node type.

# Default attribute values
sampleDefaultValue = 1

##########################################################
# Plug-in 
##########################################################
class myNode(OpenMaya.MPxNode):
    # Static variables which will later be replaced by the node's attributes.
    sampleInAttribute = OpenMaya.MObject()
    sampleOutAttribute = OpenMaya.MObject()
    
    def __init__(self):
        ''' Constructor. '''
        OpenMaya.MPxNode.__init__(self)
        
    def compute(self, pPlug, pDataBlock):
        '''
        Node computation method.
            - pPlug: A connection point related to one of our node attributes (could be an input or an output)
            - pDataBlock: Contains the data on which we will base our computations.
        '''
        
        if( pPlug == myNode.sampleOutAttribute ):
            
            # Obtain the data handles for each attribute
            sampleInDataHandle = pDataBlock.inputValue( myNode.sampleInAttribute )
            sampleOutDataHandle = pDataBlock.outputValue( myNode.sampleOutAttribute )
            
            # Extract the actual value associated to our sample input attribute (we have defined it as a float)
            sampleInValue = sampleInDataHandle.asFloat()
            
            
            # ... perform the desired computation ...
            
            # Set the output value.
            sampleOutDataHandle.setFloat( sampleInValue ) #  As an example, we just set the output value to be equal to the input value.
            
            # Mark the output data handle as being clean; it need not be computed given its input.
            sampleOutDataHandle.setClean()
             
        else:
            return OpenMaya.kUnknownParameter

##########################################################
# Plug-in initialization.
##########################################################
def nodeCreator():
    ''' Creates an instance of our node class and delivers it to Maya as a pointer. '''
    return  myNode() 

def nodeInitializer():
    ''' Defines the input and output attributes as static variables in our plug-in class. '''
    
    # The following MFnNumericAttribute function set will allow us to create our attributes.
    numericAttributeFn = OpenMaya.MFnNumericAttribute()
    
    #==================================
    # INPUT NODE ATTRIBUTE(S)
    #==================================
    global sampleDefaultValue
    myNode.sampleInAttribute = numericAttributeFn.create( 'myInputAttribute', 'i', 
                                                          OpenMaya.MFnNumericData.kFloat,
                                                          sampleDefaultValue )
    numericAttributeFn.writable = True 
    numericAttributeFn.storable = True 
    numericAttributeFn.hidden = False 
    myNode.addAttribute( myNode.sampleInAttribute ) # Calls the MPxNode.addAttribute function.
    
    #==================================
    # OUTPUT NODE ATTRIBUTE(S)
    #==================================
    myNode.sampleOutAttribute = numericAttributeFn.create( 'myOutputAttribute', 'o',
                                                           OpenMaya.MFnNumericData.kFloat )
    numericAttributeFn.storable = False 
    numericAttributeFn.writable = False
    numericAttributeFn.readable = True 
    numericAttributeFn.hidden = False 
    myNode.addAttribute( myNode.sampleOutAttribute )
    
    #==================================
    # NODE ATTRIBUTE DEPENDENCIES
    #==================================
    # If sampleInAttribute changes, the sampleOutAttribute data must be recomputed.
    myNode.attributeAffects( myNode.sampleInAttribute, myNode.sampleOutAttribute )
    
    
def initializePlugin( mobject ):
    ''' Initialize the plug-in '''
    mplugin = OpenMaya.MFnPlugin( mobject )
    try:
        mplugin.registerNode( kPluginNodeName, kPluginNodeId, nodeCreator,
                              nodeInitializer, OpenMaya.MPxNode.kDependNode, kPluginNodeClassify )
    except:
        sys.stderr.write( 'Failed to register node: ' + kPluginNodeName )
        raise
    
def uninitializePlugin( mobject ):
    ''' Uninitializes the plug-in '''
    mplugin = OpenMaya.MFnPlugin( mobject )
    try:
        mplugin.deregisterNode( kPluginNodeId )
    except:
        sys.stderr.write( 'Failed to deregister node: ' + kPluginNodeName )
        raise

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

import maya.cmds as cmds

cmds.loadPlugin( 'pySampleDGNode.py' )
cmds.createNode( 'myNodeName' )
# ...

'''
Python API 1.0:
# sampleDGNode.py

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

# Plug-in information:
kPluginNodeName = 'myNodeName'              # The name of the node.
kPluginNodeClassify = 'utility/general'     # Where this node will be found in the Maya UI.
kPluginNodeId = OpenMaya.MTypeId( 0x87EFE ) # A unique ID associated to this node type.

# Default attribute values
sampleDefaultValue = 1

##########################################################
# Plug-in 
##########################################################
class myNode(OpenMayaMPx.MPxNode):
    # Static variables which will later be replaced by the node's attributes.
    sampleInAttribute = OpenMaya.MObject()
    sampleOutAttribute = OpenMaya.MObject()
    
    def __init__(self):
        ''' Constructor. '''
        OpenMayaMPx.MPxNode.__init__(self)
        
    def compute(self, pPlug, pDataBlock):
        '''
        Node computation method.
            - pPlug: A connection point related to one of our node attributes (could be an input or an output)
            - pDataBlock: Contains the data on which we will base our computations.
        '''
        
        if( pPlug == myNode.sampleOutAttribute ):
            
            # Obtain the data handles for each attribute
            sampleInDataHandle = pDataBlock.inputValue( myNode.sampleInAttribute )
            sampleOutDataHandle = pDataBlock.outputValue( myNode.sampleOutAttribute )
            
            # Extract the actual value associated to our sample input attribute (we have defined it as a float)
            sampleInValue = sampleInDataHandle.asFloat()
            
            
            # ... perform the desired computation ...
            
            # Set the output value.
            sampleOutDataHandle.setFloat( sampleInValue ) #  As an example, we just set the output value to be equal to the input value.
            
            # Mark the output data handle as being clean; it need not be computed given its input.
            sampleOutDataHandle.setClean()
             
        else:
            return OpenMaya.kUnknownParameter

##########################################################
# Plug-in initialization.
##########################################################
def nodeCreator():
    ''' Creates an instance of our node class and delivers it to Maya as a pointer. '''
    return OpenMayaMPx.asMPxPtr( myNode() )

def nodeInitializer():
    ''' Defines the input and output attributes as static variables in our plug-in class. '''
    
    # The following MFnNumericAttribute function set will allow us to create our attributes.
    numericAttributeFn = OpenMaya.MFnNumericAttribute()
    
    #==================================
    # INPUT NODE ATTRIBUTE(S)
    #==================================
    global sampleDefaultValue
    myNode.sampleInAttribute = numericAttributeFn.create( 'myInputAttribute', 'i', 
                                                          OpenMaya.MFnNumericData.kFloat,
                                                          sampleDefaultValue )
    numericAttributeFn.setWritable( True )
    numericAttributeFn.setStorable( True ) 
    numericAttributeFn.setHidden( False )
    myNode.addAttribute( myNode.sampleInAttribute ) # Calls the MPxNode.addAttribute function.
    
    #==================================
    # OUTPUT NODE ATTRIBUTE(S)
    #==================================
    myNode.sampleOutAttribute = numericAttributeFn.create( 'myOutputAttribute', 'o',
                                                           OpenMaya.MFnNumericData.kFloat )
    numericAttributeFn.setStorable( False )
    numericAttributeFn.setWritable( False )
    numericAttributeFn.setReadable( True )
    numericAttributeFn.setHidden( False )
    myNode.addAttribute( myNode.sampleOutAttribute )
    
    #==================================
    # NODE ATTRIBUTE DEPENDENCIES
    #==================================
    # If sampleInAttribute changes, the sampleOutAttribute data must be recomputed.
    myNode.attributeAffects( myNode.sampleInAttribute, myNode.sampleOutAttribute )
    
    
def initializePlugin( mobject ):
    ''' Initialize the plug-in '''
    mplugin = OpenMayaMPx.MFnPlugin( mobject )
    try:
        mplugin.registerNode( kPluginNodeName, kPluginNodeId, nodeCreator,
                              nodeInitializer, OpenMayaMPx.MPxNode.kDependNode, kPluginNodeClassify )
    except:
        sys.stderr.write( 'Failed to register node: ' + kPluginNodeName )
        raise
    
def uninitializePlugin( mobject ):
    ''' Uninitializes the plug-in '''
    mplugin = OpenMayaMPx.MFnPlugin( mobject )
    try:
        mplugin.deregisterNode( kPluginNodeId )
    except:
        sys.stderr.write( 'Failed to deregister node: ' + kPluginNodeName )
        raise

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

import maya.cmds as cmds

cmds.loadPlugin( 'sampleDGNode.py' )
cmds.createNode( 'myNodeName' )
# ...

'''

Maya プラグイン エントリ/エグジット ポイント

ディペンデンシー グラフ プラグインは、コマンド プラグインと同じエントリ/エグジット ポイント関数を必要とします。すなわち、initializePlugin()uninitializePlugin() です。Maya がプラグインをロード/アンロードするときに、これらの 2 つの関数がそれぞれ呼び出されます。これらの 2 つの関数がファイルにない場合は、プラグインはロードに失敗します。ディペンデンシー グラフ プラグインを使用して、MPxNode またはその多くのサブクラスのいずれかを継承し、新しいノード タイプを作成します。このトピックに含まれるサンプルでは、MPxNode を直接継承しているので、MFnPlugin.registerNode() を介してノードを登録する必要があります。

def initializePlugin( mobject ):
    ''' Initialize the plug-in '''
    mplugin = OpenMayaMPx.MFnPlugin( mobject )
    try:
        mplugin.registerNode( kPluginNodeName, kPluginNodeId, nodeCreator,
                              nodeInitializer, OpenMayaMPx.MPxNode.kDependNode, kPluginNodeClassify )
    except:
        sys.stderr.write( 'Failed to register node: ' + kPluginNodeName )
        raise
    
def uninitializePlugin( mobject ):
    ''' Uninitializes the plug-in '''
    mplugin = OpenMayaMPx.MFnPlugin( mobject )
    try:
        mplugin.deregisterNode( kPluginNodeId )
    except:
        sys.stderr.write( 'Failed to deregister node: ' + kPluginNodeName )

MFnPlugin.registerNode() への呼び出しに 6 つのパラメータが含まれていることが分かります。これらのパラメータの説明は次のとおりです。

  1. ノード名: ノードの名前です。任意の名前の付いた変数 kPluginNodeName でこの値を指定します。この変数は、uninitializePlugin() の他に、sys.stderr.write() へのエラー レポート呼び出しでも使用されます。

    kPluginNodeName = 'myNodeName'              # The name of the node.

    このノードの名前を登録することにより、次の Python コマンドを使用してノードのインスタンスを作成することができます。

    import maya.cmds as cmds
    cmds.createNode( 'myNodeName' )
  2. ノード ID: Maya で登録されている他のすべてのノード タイプからノードのタイプ(MTypeId)を識別する一意の数値です。任意の名前の付いた変数 kPluginNodeId でこの値を指定します。

    kPluginNodeId = OpenMaya.MTypeId( 0x87EFE ) # A unique ID associated to this node type.

    この MTypeId 値は一意である必要があります。Maya がノードを含むファイルを保存およびロードしようとするときは、この一意の ID を使用して、ノードがラベル付けされます。ロードされたノードの一意の ID が Maya に登録されていない場合は(たぶん、プラグインがロードされていないため)、これらのノードがディペンデンシー グラフに表示されません。MTypeId API ドキュメントで詳細に説明したように、MTypeId の特定の数値範囲は別の目的のために予約されています。次の表に、これらの範囲の概要を示します。

    予約済み MTypeId 範囲 修正される問題
    0x00000 - 0x7ffff 広く分配するように意図されていないプラグインです。この範囲は内部で開発されたプラグインに適しています。
    0x80000 - 0xfffff Maya および API ドキュメントで提供されるサンプル プラグインです。
    注:作成したディペンデンシー グラフ プラグインをパブリックに分配する場合は、グローバルに一意の ID が必要になります。Autodesk 開発者ネットワークでは、こうした ID を 64、128、256、または 512 のブロックで提供します。1 つまたは複数の 24 ビットのプリフィックスが割り当てられます。これを取得したら、2 つの unsigned int パラメータを受け取る MTypeId コンストラクタを使用します。プリフィックスが最初のパラメータになりますが、2 番目のパラメータになる ID の割り当てを管理する必要があります。
  3. ノードの作成関数リファレンス: 任意の名前が付けられた nodeCreator() 関数へのリファレンスです。この関数は、ノードのインスタンスを作成し、有効な Maya オブジェクト ポインタとしてそのインスタンスを返します。

    def nodeCreator():
        ''' Creates an instance of our node class and delivers it to Maya as a pointer. '''
        return OpenMayaMPx.asMPxPtr( myNode() )
    
  4. ノードのアトリビュートの初期化関数リファレンス: 任意の名前が付けられた nodeInitializer() 関数へのリファレンスです。この関数は、ノードの入力/出力アトリビュートのセットを作成します。

    def nodeInitializer():
        ''' Defines the input and output attributes as static variables in our plug-in class. '''
        
        # The following MFnNumericAttribute function set will allow us to create our attributes.
        numericAttributeFn = OpenMaya.MFnNumericAttribute()
        
        #==================================
        # INPUT NODE ATTRIBUTE(S)
        #==================================
        global sampleDefaultValue
        myNode.sampleInAttribute = numericAttributeFn.create( 'myInputAttribute', 'i', 
                                                              OpenMaya.MFnNumericData.kFloat,
                                                              sampleDefaultValue )
        numericAttributeFn.setWritable( True )
        numericAttributeFn.setStorable( True ) 
        numericAttributeFn.setHidden( False )
        myNode.addAttribute( myNode.sampleInAttribute ) # Calls the MPxNode.addAttribute function.
        
        #==================================
        # OUTPUT NODE ATTRIBUTE(S)
        #==================================
        myNode.sampleOutAttribute = numericAttributeFn.create( 'myOutputAttribute', 'o',
                                                               OpenMaya.MFnNumericData.kFloat )
        numericAttributeFn.setStorable( False )
        numericAttributeFn.setWritable( False )
        numericAttributeFn.setReadable( True )
        numericAttributeFn.setHidden( False )
        myNode.addAttribute( myNode.sampleOutAttribute )
        
        #==================================
        # NODE ATTRIBUTE DEPENDENCIES
        #==================================
        # If sampleInAttribute changes, the sampleOutAttribute data must be recomputed.
        myNode.attributeAffects( myNode.sampleInAttribute, myNode.sampleOutAttribute )

    MFnNumericAttribute 関数セットを使用して、数値アトリビュートをカプセル化する MObject のインスタンスを作成します。これらのアトリビュートには、ロング ネームとショート ネームの他、タイプおよびオプションの既定値が必要です。作成したアトリビュートを myNode クラスのスタティック変数、たとえば、myNode.sampleInAttribute として割り当てます。これらのオブジェクトをスタティック変数として割り当てると、myNode.compute() メソッドでオブジェクトを参照することができます。

    作成したアトリビュートは、MPxNode.addAttribute() によってノードのベース クラスに追加されます。これにより、ディペンデンシー グラフでノードのアトリビュートにアクセスできるようになります。

    MFnNumericAttribute.setWritable() 関数を使用すると、入力ソースの宛先としてアトリビュートに接続することができます。MFnNumericAttribute.setStorable() を使用すると、アトリビュートに関連付けられた値をファイルに保存することができます。MFnNumeric.setReadable() 関数を使用すると、このアトリビュートに関連付けられた値を他のノードの書き込み可能なアトリビュートに対する入力として使用することができます。MFnNumeric.setHidden() アトリビュートは、ユーザ インタフェースのアトリビュートを非表示にするので、このアトリビュートが誤って接続されることはありません。

    MPxNode.attributeAffects() 関数は、指定された入力に関連付けられた値が変更された場合に、ノードの compute() 関数が呼び出され、出力アトリビュートの値が再計算されるようにします。詳細については、「ディペンデンシー グラフ」を参照してください。

  5. ノード タイプ: プラグインによって定義されているノード タイプです。既定では、MPxNode.kDependNode に設定されていますが、MPxNode.kLocatorNodeMPxNode.kDeformerNodeMPxNode.kSurfaceShape などの他のタイプを指定して、ディペンデンシー グラフのさまざまな動作を定義できます。

  6. ノードの分類: ノードの分類は、レンダー ノードの作成(Create Render Node)やハイパーシェード ウィンドウなどのさまざまなレンダリング関連のユーザ インタフェースでノードが分類される方法を決定する文字列です。文字列が提供されていない場合、ノードはレンダリング ノードとみなされないので、これらのユーザ インタフェースに表示されません。

    kPluginNodeClassify = 'utility/general'     # Where this node will be found in the Maya UI.

    次の表は、有効なレンダリング ノード分類のセットを示しています。

    カテゴリ 区分文字列
    2D テクスチャ 'texture/2d'
    3D テクスチャ 'texture/3d'
    環境テクスチャ 'texture/environment'
    サーフェス マテリアル 'shader/surface'
    ボリューム マテリアル 'shader/volume'
    ディスプレイスメント マテリアル 'shader/displacement'
    ライト 'light'
    一般ユーティリティ 'utility/general'
    カラー ユーティリティ 'utlity/color'
    パーティクル ユーティリティ 'utility/particle'
    イメージ プレーン 'imageplane'
    グロー 'postprocess/opticalFX'

ノードの動作(Node Behavior)

ディペンデンシー グラフ ノード(myNode)の動作は、MPxNode を継承するクラスで定義されます。

class myNode(OpenMayaMPx.MPxNode):
    # Static variables which will later be replaced by the node's attributes.
    sampleInAttribute = OpenMaya.MObject()
    sampleOutAttribute = OpenMaya.MObject()
    
    def __init__(self):
        ''' Constructor. '''
        OpenMayaMPx.MPxNode.__init__(self)
        
    def compute(self, pPlug, pDataBlock):
        '''
        Node computation method.
            - pPlug: A connection point related to one of our node attributes (could be an input or an output)
            - pDataBlock: Contains the data on which we will base our computations.
        '''
        
        if( pPlug == myNode.sampleOutAttribute ):
            
            # Obtain the data handles for each attribute
            sampleInDataHandle = pDataBlock.inputValue( myNode.sampleInAttribute )
            sampleOutDataHandle = pDataBlock.outputValue( myNode.sampleOutAttribute )
            
            # Extract the actual value associated to our sample input attribute (we have defined it as a float)
            sampleInValue = sampleInDataHandle.asFloat()
            
            
            # ... perform the desired computation ...
            
            # Set the output value.
            sampleOutDataHandle.setFloat( sampleInValue ) #  As an example, we just set the output value to be equal to the input value.
            
            # Mark the output data handle as being clean; it need not be computed given its input.
            sampleOutDataHandle.setClean()
             
        else:
            return OpenMaya.kUnknownParameter

Maya は、nodeInitializer()MPxNode.addAttribute() で追加されたノード アトリビュートを使用して、ノードがディペンデンシー グラフでインスタンス化されたときにノードの入力/出力接続ポイントを定義します。

これらの接続ポイントはプラグと呼ばれ、MPlug のインスタンスによって表されます。nodeInitializer()MPxNode.attributeAffects() への呼び出しは、これらのプラグ間の原因となる入力 > 出力リンクを決定します。最終的に、ディペンデンシー グラフは、すべてのノード間で接続されているプラグの有向エッジを追跡します。Maya のディペンデンシー グラフの継続している評価プロセス中に、プラグに関連付けられたデータが変更された場合は、このプラグがダーティとしてマークされます。つまり、キャッシュされた値が古いので、値を再計算する必要があります。プラグ階層が再帰的にトラバースされ、残りの影響を受けるプラグがダーティとしてマークされます。

次に、Maya は、ダーティなプラグを含む各ノードで compute() メソッドを呼び出し、MPlugMDataBlock のインスタンスを渡します。渡された MPlug の関連するアトリビュートをチェックし、何を計算するか決定する必要があります。これは、MPlug のオーバロードされた等価演算子 '==' を使用して行います。

渡された MDataBlock インスタンスには、MDataHandle オブジェクトが含まれます。このオブジェクトには、それぞれのアトリビュートに関連付けられた値が格納されます。float 入力値は、次のように取得されます。

sampleInDataHandle = pDataBlock.inputValue( myNode.sampleInAttribute )
sampleInValue = sampleInDataHandle.asFloat()

compute() メソッドは、新たに計算された値を正しい出力 MDataHandle に設定することによって終了する必要があります。MDataHandle.setClean() への呼び出しによって、対応するプラグからダーティなフラグが除去されます。

compute() 関数に渡されたプラグが計算可能な出力アトリビュートに関連していない場合は、関数が OpenMaya.kUnknownParameter を返す必要があります。これにより、ベース クラスのいずれかの compute() 関数を使用してプラグの値を計算する必要があることが Maya に通知されます。