次の例では、基本的な MPxNode 親クラスから派生する単純なカスタム ディペンデンシー グラフ ノードについて説明します。単一の浮動小数値を入力値として取り、この数値の正弦値を計算し、結果を出力します。
#include <string.h> #include <iostream.h> #include <math.h> #include <maya/MString.h> #include <maya/MFnPlugin.h>
これはプラグインでもあるため、MFnPlugin.h も必要です。ただし、コマンド以外でノードを登録するには、別の方法を使用します。
#include <maya/MPxNode.h> #include <maya/MTypeId.h> #include <maya/MPlug.h> #include <maya/MDataBlock.h> #include <maya/MDataHandle.h>
これらのヘッダ ファイルは、ほとんどのプラグイン ディペンデンシー グラフ ノードで使用されます。
#include <maya/MFnNumericAttribute.h>
さまざまなアトリビュート型が数多くあります(これらについては後で説明します)。必要なアトリビュート型は、作成するノードの種類によって決まります。このサンプルでは数値データのみを使用します。
class sine : public MPxNode {
ユーザ定義ディペンデンシー グラフ ノードは、MPxNode クラスから派生します。
public: sine();
このノードのインスタンスが作成されるたびに、ノードのコンストラクタがコールされます。createNode コマンドをコールするときや MFnDependencyNode::create() メソッドを起動するときなどがこれにあたります。
virtual ~sine();
ノードを本当に削除する場合のみ、デストラクタがコールされます。Maya には元に戻す(undo)待ち行列があるので、ノードを削除しても実際にノードのデストラクタはコールされません。削除を元に戻した場合に、ノードを再作成せずに復元できるようにするためです。一般的に削除したノードのデストラクタは、元に戻す待ち行列がフラッシュされた場合にのみコールされます。
virtual MStatus compute( const MPlug& plug, MDataBlock& data );
compute() メソッドはノードの頭脳です。ノードの入力を使用してノードの実際の作業を実行し、出力を生成します。
static void* creator();
creator() メソッドは、コマンドの creator メソッドと同じ目的で動作します。つまり、Maya でこのノードのインスタンスをインスタンス化できるようにします。createNode コマンドまたは MFnDependencyNode::create() メソッドがノードの新しいインスタンスを要求するたびに、creator() メソッドがコールされます。
static MStatus initialize();
initialize() メソッドは、ディペンデンシー ノードのための登録メカニズムでコールされます。このため、ユーザ定義ノードを含むプラグインがロードされた直後に一度コールされます。ノードの入出力(たとえばアトリビュート)を定義するために使用されます。
この 2 つの MObject は正弦(sine)ノードのアトリビュートです。ノードのアトリビュートには任意の名前を自由に使用できます。ここでは分かりやすくするため、入力(input)と出力(output)を使用します。
static MTypeId id;
それぞれのノードには固有の識別子が必要です。MFnDependencyNode::create() は、この識別子を使用して作成するノードを識別します。Maya ファイル フォーマットでも、この識別子が使用されます。
ノードをローカルでテストする場合は、0x00000000 から 0x0007ffff までの識別子を任意に使用できますが、永続的な目的でノードを使用する場合は、世界で固有な識別子を http://mayaid.autodesk.io/ から取得してください。ユーザごとに独自で管理できる、固有の範囲が割り当てられます。
}; MTypeId sine::id( 0x80000 );
ここでは、ノードの識別子を一意のタグに初期化します。これらのタグは、すべてのノードで一意にする必要があります(タグはノードを再作成するためにファイル フォーマットで使用されます)。タグは Autodesk から API ユーザに割り当てます。
void* sine::creator() { return new sine; }
前に説明したように、creator() メソッドは、このノードの新しいインスタンスを単に返します。複数のノードを相互に接続する必要がある、より複雑な状況では、接続される複数のノード用に 1 つの creator を定義し、この creator を使って割り当てすべてのノードを相互に接続することができます。
MStatus sine::initialize() {
initialize メソッドは、ノードを初めて Maya に登録する際、一度だけコールされます。このメソッドでは、ノードのアトリビュート、つまりその他のノードからの接続の対象となる、ノードの入出力データを定義します。
MFnNumericAttribute nAttr;
このサンプルでは数値データしか使用しないので、すべてのアトリビュートは数値であり MFnNumericAttribute のみが必要になります。
output = nAttr.create( "output", "out", MFnNumericData::kFloat, 0.0 ); nAttr.setWritable(false); nAttr.setStorable(false);
この 3 行の最初の行は、正弦ノードの出力アトリビュートを定義します。アトリビュートを定義する場合は、アトリビュートのロング ネーム(4 文字以上)とショート ネーム(3 文字以下)を指定する必要があります。この名前は、MEL スクリプトと UI エディタで特定アトリビュートを識別するために使用されます。必須ではありませんが、一般的にはロング ネームをアトリビュートの C++ 識別子と同じにします。このサンプルでは、両方とも出力(output)です。
create メソッドではアトリビュートの型も指定します。この場合は float (MFnNumbericData::kFloat)です。また既定値をゼロに設定します。アトリビュートの名前を一意にする必要があるのは同一ノード内のみです。別のノードでは同じ名前のアトリビュートを使用できます。
次の 2 行は、このアトリビュートの特定の特性を設定します。これは正弦ノードの出力であるため、その他のノードからは書き込めません。つまり、別のノードの出力アトリビュートは、このアトリビュートに接続できません。またこれは出力であるため、ファイルの書き込み時に保存する必要はありません。出力は入力から生成できるからです(出力を保存しても問題にはなりませんが、スペースの浪費になります)。
新しいノードをインスタンス化すると、以下の特性は true に設定されます。
input = nAttr.create( "input", "in", MFnNumericData::kFloat, 0.0 ); nAttr.setStorable(true);
入力アトリビュートの初期化は出力アトリビュートと同じですが、入力の値はノードで計算できないので、ノードを保存する際に保存する必要があります。
addAttribute( input ); attributeAffects( input, output );
入力アトリビュートが正弦ノードのアトリビュートとして追加されます。attributeAffects() メソッドは、入力アトリビュートが出力アトリビュートに影響するタイミングを示すために使用されます。これを認識すると、Maya は、複数の入出力がある、より複雑なノードのグラフでディペンデンシーを最適化できるようになりますが、すべての入力がすべての出力に影響するとは限りません。
addAttribute( output );
出力アトリビュートが正弦ノードに追加されます。出力アトリビュートが生成されても入力アトリビュートは影響されないので、attributeAffects() メソッドは必要ありません。
return MS::kSuccess;
成功を返し、ノードが問題なく初期化されたことを Maya に通知します。エラーが返されると初期化は停止します。initialize メソッドは一度しかコールされないので、ノードはこのセッションで使用できなくなります。ノードが必要とするリソースを使用できない場合は、常にエラーが返されます。
} sine::sine() {} sine::~sine() {}
非常に単純なノードなので、コンストラクタとデストラクタは何も実行しません。
MStatus sine::compute( const MPlug& plug, MDataBlock& data ) {
compute() メソッドはディペンデンシー グラフ ノードの頭脳で、ノードの実際の作業をすべて実行します。このメソッドは 2 つの引数を取ります。最初の引数は、計算を要求するプラグへの参照で、次の引数はノードのデータ ブロックです。プラグとデータ ブロックについては、後のセクションで詳しく説明します。
MStatus returnStatus; if( plug == output ) {
プラグは、再計算されたアトリビュートと考えることができます。このテストでは、再計算が要求されている出力アトリビュートがチェックされます。
この単純なサンプルでは出力アトリビュートしか該当しませんが、より複雑なノードでは、任意の出力になることがあります。プラグが認識されているアトリビュートを表しているかどうか、常にテストしてください。たとえば、誰かが接続のあるノードにダイナミック アトリビュートをアタッチしたとします。これにより、入力が変更されていないのに compute メソッドがコールされることがあります。
MDataHandle inputData = data.inputValue( input, &returnStatus );
データ ブロックには、ノードのこのインスタンスのデータがすべて含まれています。効率上の理由から、このデータは 1 つのブロックとして保存されます。ブロックの一部を参照するには、データ ハンドルを使用します。このケースでは、入力アトリビュートを参照するためにデータ ハンドルが設定されています。
if( returnStatus != MS::kSuccess ) cerr << "ERROR getting data" << endl; else { float result = sin( inputData.asFloat() );
MFnDataHandle の as*() メソッドによって、データ ハンドルからデータが取り出されます。as*() メソッドは必ず、宣言されたアトリビュートの型と一致させる必要があります。入力アトリビュートを MFnNumericAttribute::kFloat として宣言したので、データは asFloat() で取り出す必要があります。型と取り出しメソッドが混在すると、重大なエラーの原因となります。たとえばアトリビュートを MFnNumericAttribute::kDouble として宣言し、asFloat() で取り出すと重大なエラーとなります。
MDataHandle outputHandle = data.outputValue( output );
出力アトリビュートのデータ ブロックの一部を参照するために、新しいデータ ハンドルを割り当てます。
outputHandle.set( result );
data.setClean(plug);
再計算を引き起こしたデータ ブロックのプラグが clean にマークされ、新しく計算し直されたことが示されます。
} } return MS::kSuccess; }
成功ステータスが返ると、計算が適切に終了したことが分かります。
MStatus initializePlugin( MObject obj ) { MStatus status; MFnPlugin plugin( obj, "My plug-in", "1.0", "Any"); status = plugin.registerNode( "sine", sine::id, sine::creator, sine::initialize );
プラグインでは、コマンド プラグインと同じように initializePlugin() と uninitializePlugin() が必要ですが、registerCommand() の代わりに registerNode() を使用し、Maya のノードのデータベースにノードを追加します。ノードの initialize メソッドは、registerNode をコールした結果としてコールされます。initialize メソッドがエラー コードを返すと、registerNode もエラーになり、ノードはロードされません。
return status; } MStatus uninitializePlugin( MObject obj) { MStatus status; MFnPlugin plugin( obj ); status = plugin.deregisterNode( sine::id ); return status; }