MPxCommand

先ほどの新しい hello コマンドでは、プロキシ オブジェクトを使用して新しい機能を Maya に追加しています( MFnPlugin を参照)。このプロキシ オブジェクトは MPxCommand から派生します。MPxCommand は、コマンドを内蔵コマンドのように扱うために必要なすべての機能を提供します。

最低でも 2 つのメソッドを定義する必要があります。2 つのメソッドとは、doIt() メソッドと creator です。

class hello : public MPxCommand
{
public:
 	Status doIt( const MArgList& args );
	static void* creator();
};

doIt() メソッドと redoIt() メソッド

doIt() メソッドは純粋仮想メソッドです。ベース クラス内で creator が定義されていないので、doIt() と creator の両方を定義する必要があります。

単純なコマンドの場合、doIt() メソッドはコマンドのアクションを実行します。複雑なコマンドの場合、doIt() メソッドは、引数リスト、セレクション リスト、その他必要なものを解析します。次にこの情報を使用してコマンドの内部にデータを設定してから redoIt() メソッドをコールし、一括して作業を実行します。これにより、doIt() メソッドと redoIt() メソッドの間でコードの重複を避けることができます。

メソッドがコールされるタイミングを確認する

次の単純なプラグインでは、Maya によってプラグインのメソッドのどれかがコールされた場合に文字列が出力されます。これを使用すると、メソッドがコールされるタイミングを確認できます。

#include <stdio.h>
#include <maya/MString.h>
#include <maya/MArgList.h>
#include <maya/MFnPlugin.h>
#include <maya/MPxCommand.h>
#include <maya/MIOStream.h>
class commandExample : public MPxCommand
{
    public:
        commandExample();
        virtual ~commandExample();
        MStatus doIt( const MArgList& );
        MStatus redoIt();
        MStatus undoIt();
        bool isUndoable() const;
        static void* creator();
};
commandExample::commandExample() {
    cout << "In commandExample::commandExample()\n";
}
commandExample::~commandExample() {
    cout << "In commandExample::~commandExample()\n";
}
MStatus commandExample::doIt( const MArgList& ) {
    cout << "In commandExample::doIt()\n";
    return MS::kSuccess;
}
MStatus commandExample::redoIt() {
    cout << "In commandExample::redoIt()\n";
    return MS::kSuccess;
}
MStatus commandExample::undoIt() {
    cout << "In commandExample::undoIt()\n";
    return MS::kSuccess;
}
bool commandExample::isUndoable() const {
    cout << "In commandExample::isUndoable()\n";
    return true;
}
void* commandExample::creator() {
    cout << "In commandExample::creator()\n";
    return new commandExample();
}
MStatus initializePlugin( MObject obj )
{
    MFnPlugin plugin( obj, "My plug-in", "1.0", "Any" );
    plugin.registerCommand( "commandExample", commandExample::creator );
    cout << "In initializePlugin()\n";
    return MS::kSuccess;
}
MStatus uninitializePlugin( MObject obj )
{
    MFnPlugin plugin( obj );
    plugin.deregisterCommand( "commandExample" );
    cout << "In uninitializePlugin()\n";
    return MS::kSuccess;
}

このプラグインを初めてロードすると、すぐに「In initializePlugin()」と出力されます。コマンド ウィンドウに「commandExample」と入力すると、以下が出力されます。

In commandExample::creator()
In commandExample::commandExample()
In commandExample::doIt()
In commandExample::isUndoable()

デストラクタがコールされていないことに注意してください。コマンド オブジェクトを永続的に残し、元に戻すややり直し(取り消された後)ができるようにするためです。

Maya の元に戻す(undo)メカニズムはこのように機能します。コマンド オブジェクトには、必要な場合に自分自身を元に戻す(undo)ことができるようにする情報が含まれています。元に戻す待ち行列の末尾からコマンドが落ちた場合、取り消されてやり直しされない場合、あるいはプラグインがアンロードされた場合に、デストラクタがコールされます。

編集 > 元に戻す(Edit > Undo) (または MEL の undo コマンド)と編集 > やり直し(Edit > Redo) (または MEL の redo コマンド)を使用すると、これらのメニュー項目がコールされるときに、コマンドの undoIt() メソッドと redoIt() メソッドがコールされます。

この例を、isUndoable() メソッドが true ではなく false を返すように変更すると(リコンパイルする前に、忘れずにプラグインをアンロードしてください)、実行時に次のように出力されます。

In commandExample::creator()
In commandExample::commandExample()
In commandExample::doIt()
In commandExample::isUndoable()
In commandExample::~commandExample()

この場合は、コマンドを元に戻すことができないので、デストラクタがすぐにコールされます。Maya では、元に戻すことができないコマンドはシーンにまったく影響しないアクションとして扱います。つまりコマンドの実行後に情報を保存する必要がなく、コマンドの元に戻すとやり直しが要求されても、何も変更されていないのでそれらは実行されません。

元に戻すとやり直しを含むらせんのサンプル

以下は、らせんのプラグインを別の方法で実装したサンプルです。このバージョンは、元に戻すとやり直しを含む、フル コマンドとして実装されています。選択したカーブを処理してらせんに変形するというように動作します。

#include <stdio.h>
#include <math.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnNurbsCurve.h>
#include <maya/MPointArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MPoint.h>
#include <maya/MSelectionList.h>
#include <maya/MItSelectionList.h>
#include <maya/MItCurveCV.h>
#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MString.h>
#include <maya/MPxCommand.h>
#include <maya/MArgList.h>
class helix2 : public MPxCommand {
public:
    helix2();
    virtual ~helix2();
    MStatus doIt( const MArgList& );
    MStatus redoIt();
    MStatus undoIt();
    bool isUndoable() const;
    static void* creator();

コマンドは、前のサンプルと同様に、定義するメソッドの宣言から始まります。

    private:
    MDagPath fDagPath;
    MPointArray fCVs;
    double radius;
    double pitch;
};

このコマンドはモデルを編集します。変更内容を取り消せるようにするため、カーブの元の定義を保存するスペースが割り当てられます。また、らせんの説明も保存し、redoIt メソッドがコールされた場合にらせんを再作成できるようにします。

このコマンドでは MObject のポインタを保存するのではなく、MDagPath を使用して元に戻すとやり直しのカーブを参照することに注意してください。MObject は、コマンドが次回実行された際に有効であるとは限りません。その結果、MObject を使用すると、Maya は undoIt()redoIt() を実行した場合にコア ダンプを作成する可能性が高くなります。しかし MDagPath は、カーブへのパスを記述しているだけなので、コマンドがいつ実行されても正確です。

void* helix2::creator() {
    return new helix2;
}

creator は単にオブジェクトのインスタンスを返します。

helix2::helix2() : radius( 4.0 ), pitch( 0.5 ) {}

コンストラクタが半径とピッチを初期化します。

helix2::~helix2() {}

プライベートなデータは自動的にクリーンアップされるので、デストラクタは何も実行する必要がありません。

注:

Maya が所有するデータは削除しないでください。

MStatus helix2::doIt( const MArgList& args ) {
    MStatus status;
    // Parse the arguments.
    for ( int i = 0; i < args.length(); i++ )
        if ( MString( "-p" ) == args.asString( i, &status )
            && MS::kSuccess == status )
        {
            double tmp = args.asDouble( ++i, &status );
            if ( MS::kSuccess == status )
                pitch = tmp;
        }
        else if ( MString( "-r" ) == args.asString( i, &status )
            && MS::kSuccess == status )
        {
            double tmp = args.asDouble( ++i, &status );
            if ( MS::kSuccess == status )
                radius = tmp;
        }
        else
        {
            MString msg = "Invalid flag: ";
            msg += args.asString( i );
            displayError( msg );
            return MS::kFailure;
        }

前と同じように、これは doIt() メソッドに渡された引数を解析して使用し、内部の半径フィールドとピッチ フィールドを設定します。このフィールドは redoIt() メソッドによって使用されます。doIt() メソッドのみが引数を受け取ります。undoIt() メソッドと redoIt() メソッドは、コマンド自体の内部データからそれぞれのデータを取ります。

最後の else 節では、MPxCommand から継承された displayError() メソッドが、コマンド ウィンドウとコマンド出力ウィンドウにメッセージを出力します。displayError() でのメッセージ出力には、「Error:」というプリフィックスが付いています。もう 1 つのオプションとして、メッセージに「Warning:」というプリフィックスが付く displayWarning() があります。

    // Get the first selected curve from the selection list.
    MSelectionList slist;
    MGlobal::getActiveSelectionList( slist );
    MItSelectionList list( slist, MFn::kNurbsCurve, &status );
    if (MS::kSuccess != status) {
        displayError( "Could not create selection list iterator" );
        return status;
    }
    if (list.isDone()) {
        displayError( "No curve selected" );
        return MS::kFailure;
    }
    MObject component;
    list.getDagPath( fDagPath, component );

このコードでは、セレクション リストから最初のカーブ オブジェクトを取得します。コマンドの fDagPath フィールドは、現在の選択項目に設定されます。詳細については、「MItSelectionList」を参照してください。

    return redoIt();
}

コマンドの内部データが設定されると、redoIt() メソッドがコールされます。doIt() メソッドは、必要なアクションを自分自身で実行できますが、このアクションは redoIt() が実行するアクションと常に同じなので、doIt()redoIt() をコールすると、コードの重複を減らすことができます。

なぜ doIt()redoIt() をコールするのでしょうか。逆ではないのでしょうか。redoIt() メソッドがキャッシュされたデータを取得して MArgList に変換し、doIt() に渡すこともできますが、非常に非能率的です。

MStatus helix2::redoIt()
{
    unsigned i, numCVs;
    MStatus status;
    MFnNurbsCurve curveFn( fDagPath );
    numCVs = curveFn.numCVs();
    status = curveFn.getCVs( fCVs );
    if ( MS::kSuccess != status )
    {
        displayError( "Could not get curve’s CVs" );
        return MS::kFailure;
    }

このコードでは選択したカーブの CV を取得し、コマンドの内部 MPointArray に保存します。保存された CV の位置は、カーブを元の形に戻す undoIt() メソッドがコールされるときに使用されます。

    MPointArray points(fCVs);
    for (i = 0; i < numCVs; i++)
    points[i] = MPoint( radius * cos( (double)i ),
        pitch * (double)i, radius * sin( (double)i ) );
    status = curveFn.setCVs( points );
    if ( MS::kSuccess != status )
    {
        displayError( "Could not set new CV information" );
        fCVs.clear();
        return status;
    }

このコードでは、これまでに紹介したらせんのサンプルと同じように、カーブの CV の位置を設定してらせんを形成します。

    status = curveFn.updateCurve();
    if ( MS::kSuccess != status )
    {
        displayError( "Could not update curve" );
        return status;
    }

updateCurve() メソッドを使用し、カーブのジオメトリが変更されたことを Maya に通知します。ジオメトリが変更された後でこのメソッドをコールしないと、オブジェクトの表示は変更されません。

    return MS::kSuccess;
}

関数の完了時に MS::kSuccess を返すことにより、操作が問題なく完了したことが Maya に通知されます。

MStatus helix2::undoIt()
{
    MStatus status;
    MFnNurbsCurve curveFn( fDagPath );
    status = curveFn.setCVs( fCVs );
    if ( MS::kSuccess != status)
    {
        displayError( "Could not set old CV information" );
        return status;
    }

この数行では、保存された CV の位置(カーブの CV の元の位置)を取得してそれを再設定します。

注:

変更される CV の数、または undo 関数で削除されるカーブについて気にする必要はありません。コマンドが元に戻された後に実行されたものはすべて、undoIt() がコールされる前に取り消されます。その結果、モデルはコマンドが完了した直後の状態に戻ります。

    status = curveFn.updateCurve();
    if ( MS::kSuccess != status )
    {
        displayError( "Could not update curve" );
        return status;
    }
    fCVs.clear();
    return MS::kSuccess;
}

ここでは、念のため MPointArray がクリアされます。

bool helix2::isUndoable() const
{
    return true;
}

このコマンドは元に戻すことができます。このコマンドはモデルを編集しますが、undoIt() メソッドが提供されているので、コマンドが実行される前の状態にモデルを戻すことができます。

MStatus initializePlugin( MObject obj )
{ 
    MFnPlugin plugin( obj, "Autodesk", "1.0", "Any");
    plugin.registerCommand( "helix2", helix2::creator );
    return MS::kSuccess;
}
MStatus uninitializePlugin( MObject obj )
{
    MFnPlugin plugin( obj );
    plugin.deregisterCommand( "helix2" );
    return MS::kSuccess;
}

プラグインは、通常の initialize 関数と uninitialize 関数で終了します。