MPxToolCommand

 
 
 

The MPxToolCommand is the base class for creating commands that can be executed from within a context. Tool commands are similar to regular commands in that they are defined with command flags and can be executed from the Maya command line. However, they must perform extra duties, as the actions taken by a context do not come from the normal Maya command mechanism but from inside the methods of the MPxContext class. These duties are to alert Maya of the execution of the command so that the undo/redo and journalling mechanisms operate correctly on the command. The MPxToolCommand is a subclass of MPxCommand with the additional methods.

If a context wants to perform its own command, it must register the command when the context and context command are registered. A context can only have one tool command associated with it.

The following is an example of a tool command, the helixTool. As with the marqueeTool, the list of include files is omitted for brevity. See the helixTool.cpp file in devkit/plug-ins for the complete example.

#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();

The set of methods on MPxToolCommand are similar to those on MPxCommand but with the addition of finalize(). The finalize method is used to create a string representing the command and its arguments.

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

These methods are necessary since the properties of the helix will be set from the context object.

    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() {}

These first two methods are identical to the earlier "helix2" example.

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

The constructor saves away the name of the MEL command for later use in the finalize() method.

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;
}

This method is similar to the earlier helix example—it parses the arguments and uses them to set its internal state. In general, this command will be used through the UI, but since it is still a MEL command, it can be invoked from the MEL command shell.

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;
}

This is essentially the same as the earlier helix example.

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

Again this is essentially the same as the earlier helix example. You should be noticing a pattern developing. It is quite easy to change a command into a tool. Little of the command has to be changed, additions just have to be made to hook the tool up to the UI.

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

This tool is undoable.

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 );
}

This method is the one noticeable addition to the tool which wasn’t necessary in the command. When a command is typed in it is easy to take it and print it out to a journal file. Since a tool is not typed in, but created through mouse input, no text string exists to be output to a journal file. The finalize() method solves this by outputting a string when the tool has completed. It is necessary for you to call MPxToolCommand::doFinalize() to have the command output to the journal file.

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
{

This is the context which will be executing the helixTool command.

    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 );

The set of methods are the same as for the marqueeTool example.

    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;
}

These three methods are essentially the same as the marqueeTool examples, the only difference being that doPress() for the helixTool does not need to determine the modifier key state.

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();

This code draws a cylinder in the current view defining the outlines of the helix that will be generated.

#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();

The user has released the mouse so this code cleans up the OpenGL drawing.

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

This code creates the actual helixTool command, by calling the helixTool::creator method that you will register later, sets the radius and pitch, and then calls the redoIt() method to generate the data. As a last step, the finalize() method is called to ensure that this command is written out to the journal file.

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

The next four methods are used in the interaction between the context and the contextCommand’s edit and query methods. These will be called by the tool property sheet for the context. The MToolsInfo::setDirtyFlag() method alerts the tool property sheet so it can redraw itself with the new property values for the context.

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;
}

The next class and implementation repeats the code from the marqueeTool example. This class is necessary to create instances of the tool context.

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;
}

The next two methods handle the argument parsing for the command. There are two types of arguments—those which make modifications to the properties of a context, and those which query the properties of a context.

Note

Argument parsing is done through the MPxContextCommand::parser() method which returns an MArgParser. This class is analogous to the MArgDatabase class that is used with the MPxCommand class.

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;
}

The initializePlugin() method registers both the helix command and the context via a single register call.

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;
}

MEL code similar to the marqueeTool example’s is necessary to attach the helixTool to the UI.