MPxToolCommand

MPxToolCommand は、コンテキスト内から実行できるコマンドを作成するベース クラスです。ツール コマンドは、コマンド フラグとともに定義され、Maya コマンド ラインから実行できるという点で通常のコマンドと同じです。しかしツール コマンドの場合、コンテキストによって実行されるアクションは通常の Maya コマンド メカニズムではなく MPxContext クラスのメソッド内に存在するため、追加作業が発生します。作業とは、コマンドが実行されたことを Maya に通知することです。これにより、コマンドの元に戻す/やり直しメカニズムとジャーナリング メカニズムが正しく動作するようになります。MPxToolCommand は追加のメソッドを含む、MPxCommand のサブクラスです。

コンテキストで独自のコマンドを実行するには、コンテキストとコンテキスト コマンドを登録する際にコマンドを登録する必要があります。コンテキストには、1 つのツール コマンドしか関連付けられません。

以下は、ツール コマンド helixTool のサンプルです。marqueeTool コマンドと同じように、簡潔にするため、インクルード ファイルのリストは省略しました。完全版のサンプルについては、Developer Kit の helixTool.cpp ファイルを参照してください。

#define NUMBER_OF_CVS 20
class helixTool : public MPxToolCommand
{
    public:
        helixTool(); 
        virtual ~helixTool(); 
        static void* creator();
        MStatus doIt( const MArgList& args );
        MStatus redoIt();
        MStatus undoIt();
        bool isUndoable() const;
        MStatus finalize();
        static MSyntax newSyntax();

MPxToolCommand のメソッド セットは、MPxCommand のメソッド セットと似ていますが、finalize() が追加されています。finalize メソッドは、コマンドとその引数を表す文字列を作成するために使用されます。

        void setRadius( double newRadius );
        void setPitch( double newPitch );
        void setNumCVs( unsigned newNumCVs );
        void setUpsideDown( bool newUpsideDown );

これらのメソッドは、らせんのプロパティがコンテキスト オブジェクトから設定されているので必要になります。

    private:
        double radius; // Helix radius
        double pitch; // Helix pitch
        unsigned numCV; // Helix number of CVs
        bool upDown; // Helis upsideDown
        MDagPath path; // Dag path to the curve.
        // Don’t save the pointer!
};
void* helixTool::creator()
{
    return new helixTool;
}
helixTool::~helixTool() {}

最初の 2 つのメソッドは、前の「helix2」のサンプルと同じです。

helixTool::helixTool()
{ 
    numCV = NUMBER_OF_CVS;
    upDown = false;
    setCommandString( "helixToolCmd" );
}

コンストラクタは、後で finalize() メソッドで使用するために MEL コマンドの名前を保存します。

MSyntax helixTool::newSyntax()
{
    MSyntax syntax;
    syntax.addFlag(kPitchFlag, kPitchFlagLong,
    MSyntax::kDouble);
    syntax.addFlag(kRadiusFlag, kRadiusFlagLong,
    MSyntax::kDouble);
    syntax.addFlag(kNumberCVsFlag, kNumberCVsFlagLong,
    MSyntax::kUnsigned);
    syntax.addFlag(kUpsideDownFlag, kUpsideDownFlagLong,
    MSyntax::kBoolean);
    return syntax;
}
MStatus helixTool::doIt( const MArgList &args )
{
    MStatus status;
    status = parseArgs(args);
    if (MS::kSuccess != status)
        return status;
    return redoIt();
}
MStatus helixTool::parseArgs(const MArgList &args)
{
    MStatus status;
    MArgDatabase argData(syntax(), args);
    if (argData.isFlagSet(kPitchFlag)) {
        double tmp;
        status = argData.getFlagArgument(kPitchFlag, 0, tmp);
        if (!status) {
            status.perror("pitch flag parsing failed.");
            return status;
        }
        pitch = tmp;
    }
    if (argData.isFlagSet(kRadiusFlag)) {
        double tmp;
        status = argData.getFlagArgument(kRadiusFlag, 0, tmp);
        if (!status) {
            status.perror("radius flag parsing failed.");
            return status;
        }
        radius = tmp;
    }
    if (argData.isFlagSet(kNumberCVsFlag)) {
        unsigned tmp;
        status = argData.getFlagArgument(kNumberCVsFlag,
            0, tmp);
        if (!status) {
            status.perror("numCVs flag parsing failed.");
            return status;
        }
        numCV = tmp;
    }
    if (argData.isFlagSet(kUpsideDownFlag)) {
        bool tmp;
        status = argData.getFlagArgument(kUpsideDownFlag,
            0, tmp);
        if (!status) {
            status.perror("upside down flag parsing failed.");
            return status;
        }
        upDown = tmp;
    }
    return MS::kSuccess;
}

このメソッドは前のらせんのサンプルに似ています。つまり、引数を解析し、それを使用して内部状態を設定しています。このコマンドは通常 UI から使用しますが、MEL コマンドなので、MEL コマンド シェルから起動できます。

MStatus helixTool::redoIt()
{
    MStatus stat;
    const unsigned deg = 3; // Curve Degree
    const unsigned ncvs = NUMBER_OF_CVS;// Number of CVs
    const unsigned spans = ncvs - deg; // Number of spans
    const unsigned nknots = spans+2*deg-1;// Number of knots
    unsigned i;
    MPointArray controlVertices;
    MDoubleArray knotSequences;
    int upFactor;
    if (upDown) upFactor = -1;
    else upFactor = 1;
    // Set up cvs and knots for the helix
    //
    for (i = 0; i < ncvs; i++)
        controlVertices.append( MPoint(
        radius * cos( (double)i ),
        upFactor * pitch * (double)i,
        radius * sin( (double)i ) ) );
    for (i = 0; i < nknots; i++)
        knotSequences.append( (double)i );
    // Now create the curve
    //
    MFnNurbsCurve curveFn;
    MObject curve = curveFn.create( controlVertices,
        knotSequences, deg, MFnNurbsCurve::kOpen,
        false, false, MObject::kNullObj, &stat );
    if ( !stat )
    {
        stat.perror("Error creating curve");
        return stat;
    }
    stat = curveFn.getPath( path );
    return stat;
}

これは、前のらせんのサンプルと本質的に同じです。

MStatus helixTool::undoIt()
{
    MStatus stat; 
    MObject transform = path.transform();
    stat = MGlobal::removeFromModel( transform );
    return stat;
}

これも、前のらせんのサンプルと本質的に同じです。パターンが出来てきていることに注意してください。コマンドをツールに変更するのは非常に簡単です。ツールを UI に表示するために必要なのは、ほんの少しのコマンドの変更と追加だけです。

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

このツールは元に戻すことが可能です。

MStatus helixTool::finalize()
{
    MArgList command;
    command.addArg( commandString() );
    command.addArg( MString(kRadiusFlag) );
    command.addArg( radius );
    command.addArg( MString(kPitchFlag) );
    command.addArg( pitch );
    command.addArg( MString(kNumberCVsFlag) );
    command.addArg( (int)numCV );
    command.addArg( MString(kUpsideDownFlag) );
    command.addArg( upDown );
    return MPxToolCommand::doFinalize( command );
}

このメソッドが、コマンドでは必要なかった、特筆すべきツールへの追加点です。コマンドを直接入力する場合は、簡単に入力を取り込んでジャーナル ファイルに出力できます。しかしツールは直接入力ではなくマウス入力で作成されるので、ジャーナル ファイルに出力するテキスト文字列が存在しません。この問題を解決するのが finalize() メソッドです。このメソッドは、ツールが完了した際に文字列を出力します。MPxToolCommand::doFinalize() をコールし、コマンド出力をジャーナル ファイルへ出力します。

void helixTool::setRadius( double newRadius )
{
    radius = newRadius;
}
void helixTool::setPitch( double newPitch )
{
    pitch = newPitch;
}
void helixTool::setNumCVs( unsigned newNumCVs )
{
    numCV = newNumCVs;
}
void helixTool::setUpsideDown( double newUpsideDown )
{
    upDown = newUpsideDown;
}
const char helpString[] = "Click and drag to draw helix";
class helixContext : public MPxContext
{

これは、helixTool コマンドを実行するコンテキストです。

    public:
        helixContext();
        virtual void toolOnSetup( MEvent & event );
        virtual MStatus doPress( MEvent & event );
        virtual MStatus doDrag( MEvent & event );
        virtual MStatus doRelease( MEvent & event );
        virtual MStatus doEnterRegion( MEvent & event );

メソッドのセットは、marqueeTool のサンプルと同じです。

    private:
        short startPos_x, startPos_y;
        short endPos_x, endPos_y;
        unsigned numCV;
        bool upDown;
        M3dView view;
        GLdouble height,radius;
};
helixContext::helixContext() 
{
    setTitleString( "Helix Tool" );
}
void helixContext::toolOnSetup( MEvent & )
{
    setHelpString( helpString );
}
MStatus helixContext::doPress( MEvent & event )
{
    event.getPosition( startPos_x, startPos_y );
    view = MGlobal::active3dView();
    view.beginGL();
    view.beginOverlayDrawing();
    return MS::kSuccess;
}

この 3 つのメソッドは、marqueeTool のサンプルと本質的に同じです。helixTool の doPress() では、モディファイア キーの状態を判断する必要がない点だけが異なります。

MStatus helixContext::doDrag( MEvent & event )
{
    event.getPosition( endPos_x, endPos_y );
    view.clearOverlayPlane();
    glIndexi( 2 );
    int upFactor;
    if (upDown) upFactor = 1;
    else upFactor = -1;
    // Draw the guide cylinder
    //
    glMatrixMode( GL_MODELVIEW );
    glPushMatrix();
    glRotatef( upFactor * 90.0, 1.0f, 0.0f, 0.0f );
    GLUquadricObj *qobj = gluNewQuadric();
    gluQuadricDrawStyle(qobj, GLU_LINE);
    GLdouble factor = (GLdouble)numCV;
    radius = fabs(endPos_x - startPos_x)/factor + 0.1;
    height = fabs(endPos_y - startPos_y)/factor + 0.1;
    gluCylinder( qobj, radius, radius, height, 8, 1 );
    glPopMatrix();

このコードでは、生成するらせんのアウトラインを定義する円柱を現在選択しているビューに描画します。

#ifndef _WIN32
    glXSwapBuffers(view.display(), view.window() );
#else
    SwapBuffers(view.deviceContext() );
#endif
    return MS::kSuccess;
}
MStatus helixContext::doRelease( MEvent & )
{
    // Clear the overlay plane & restore from overlay drawing
    //
    view.clearOverlayPlane();
    view.endOverlayDrawing();
    view.endGL();

マウスを放すと、このコードによって OpenGL 描画がクリーンアップされます。

    helixTool * cmd = (helixTool*)newToolCommand();
    cmd->setPitch( height/NumCVs );
    cmd->setRadius( radius );
    cmd->setNumCVs( numCV );
    cmd->setUpsideDown( upDown );
    cmd->redoIt();
    cmd->finalize();

このコードでは、実際の helixTool コマンドが作成されます。ここでは、後で登録する helixTool::creator メソッドをコールし、半径とピッチを設定し、redoIt() メソッドをコールしてデータを生成します。最後に finalize() メソッドをコールし、このコマンドをジャーナル ファイルに書き出します。

    return MS::kSuccess;
}
MStatus helixContext::doEnterRegion( MEvent & )
{
    return setHelpString( helpString );
}
void helixContext::getClassName( MString &name ) const
{
    name.set("helix");
}

次の 4 つのメソッドは、コンテキストと、contextCommand の edit メソッドと query メソッドの間の対話に使用されます。これらのメソッドは、コンテキストのツール プロパティ シートでコールされます。MToolsInfo::setDirtyFlag() メソッドではツール プロパティ シートに通知を発し、ツール プロパティ シートがコンテキストの新しいプロパティ値で自分自身を再描画できるようにします。

void helixContext::setNumCVs( unsigned newNumCVs )
{
    numCV = newNumCVs;
    MToolsInfo::setDirtyFlag(*this);
}
void helixContext::setUpsideDown( bool newUpsideDown )
{
    upDown = newUpsideDown;
    MToolsInfo::setDirtyFlag(*this);
}
unsigned helixContext::numCVs()
{
    return numCV;
}
bool helixContext::upsideDown()
{
    return upDown;
}

次のクラスと実装は、marqueeTool のサンプルに登場したコードの繰り返しです。このクラスは、ツール コンテキストのインスタンスの作成に必要となります。

class helixContextCmd : public MPxContextCommand
{
    public: 
        helixContextCmd();
        virtual MStatus doEditFlags();
        virtual MStatus doQueryFlags();
        virtual MPxContext* makeObj();
        virtual MStatus appendSyntax();
        static void* creator();
    protected:
        helixContext * fHelixContext;
};
helixContextCmd::helixContextCmd() {}
MPxContext* helixContextCmd::makeObj()
{
    fHelixContext = new helixContext();
    return fHelixContext;
}
void* helixContextCmd::creator()
{
    return new helixContextCmd;
}

次の 2 つのメソッドは、コマンドの引数解析を処理します。引数には 2 種類あります。コンテキストのプロパティを修正するものと、コンテキストのプロパティを検索するものです。

注:

引数解析は、MArgParser を返す MPxContextCommand::parser() メソッドで実行します。このクラスは、MPxCommand クラスで使用する MArgDatabase クラスに似ています。

MStatus helixContextCmd::doEditFlags()
{
    MArgParser argData = parser();
    if (argData.isFlagSet(kNumberCVsFlag)) {
        unsigned numCVs;
        status = argData.getFlagArgument(kNumberCVsFlag,
            0, numCVs);
        if (!status) {
            status.perror("numCVs flag parsing failed.");
            return status;
        }
        fHelixContext->setNumCVs(numCVs);
    }
    if (argData.isFlagSet(kUpsideDownFlag)) {
        bool upsideDown;
        status = argData.getFlagArgument(kUpsideDownFlag,
            0, upsideDown);
        if (!status) {
            status.perror("upsideDown flag parsing failed.");
            return status;
        }
        fHelixContext->setUpsideDown(upsideDown);
    }

    return MS::kSuccess;
}
MStatus helixContextCmd::doQueryFlags()
{
    MArgParser argData = parser();
    if (argData.isFlagSet(kNumberCVsFlag)) {
        setResult((int) fHelixContext->numCVs());
    }
    if (argData.isFlagSet(kUpsideDownFlag)) {
        setResult(fHelixContext->upsideDown());
    }
    return MS::kSuccess;
}
MStatus helixContextCmd::appendSyntax()
{
    MStatus status;
    MSyntax mySyntax = syntax();
    if (MS::kSuccess != mySyntax.addFlag(kNumberCVsFlag,
        kNumberCVsFlagLong, MSyntax::kUnsigned)) {
            return MS::kFailure;
    }
    if (MS::kSuccess != mySyntax.addFlag(kUpsideDownFlag,
        kUpsideDownFlagLong, MSyntax::kBoolean)) {
            return MS::kFailure;
    }
    return MS::kSuccess;
}
MStatus initializePlugin( MObject obj )
{
    MStatus status;
    MFnPlugin plugin( obj, "Autodesk", "1.0", "Any");
    // Register the context creation command and the tool
    // command that the helixContext will use.
    // 
    status = plugin.registerContextCommand(
        "helixToolContext", helixContextCmd::creator,
        "helixToolCmd", helixTool::creator,
        helixTool::newSyntax);
    if (!status) {
        status.perror("registerContextCommand");
        return status;
    }
    return status;
}

initializePlugin() メソッドにより、1 回の登録コールでらせんコマンドとコンテキストの両方を登録します。

MStatus uninitializePlugin( MObject obj)
{
    MStatus status;
    MFnPlugin plugin( obj );
    // Deregister the tool command and the context
    // creation command.
    //
    status = plugin.deregisterContextCommand(
        "helixToolContext" "helixToolCmd");
    if (!status) {
        status.perror("deregisterContextCommand");
        return status;
    }
    return status;
}

helixTool を UI にアタッチするには、marqueeTool のサンプルと同じような MEL コードが必要になります。