- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Examples

# helixTool.cpp

    //-
    // ==========================================================================
    // Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
    //
    // Use of this software is subject to the terms of the Autodesk
    // license agreement provided at the time of installation or download,
    // or which otherwise accompanies this software in either electronic
    // or hard copy form.
    // ==========================================================================
    //+

    //
    // helixTool.cpp
    //
    // Description:
    //     Interactive tool to draw a helix.
    //     Uses OpenGL to draw a guideline for the helix.
    //
    //
    // Steps in creating a tool command
    //
    // 1. Create a tool command class.
    //    Same process as an MPxCommand except define 2 methods
    //    for interactive use: cancel, and finalize.
    //    There is also an addition constructor MPxToolCommand(), which
    //    is called from your context when the command needs to be invoked.
    //
    // 2. Define your context.
    //    This is accomplished by deriving off of MPxContext and overriding
    //    whatever methods you need.
    //
    // 3. Create a command class to create your context.
    //    You will call this command in Maya to create and name a context.
    //
    #include <stdio.h>
    #include <maya/MIOStream.h>
    #include <math.h>

    #include <maya/MString.h>
    #include <maya/MArgList.h>
    #include <maya/MEvent.h>
    #include <maya/MGlobal.h>
    #include <maya/M3dView.h>
    #include <maya/MPoint.h>
    #include <maya/MPointArray.h>
    #include <maya/MDoubleArray.h>
    #include <maya/MDagModifier.h>
    #include <maya/MDagPath.h>

    #include <maya/MPxContext.h>
    #include <maya/MPxContextCommand.h>
    #include <maya/MPxToolCommand.h>
    #include <maya/MToolsInfo.h>

    #include <maya/MFnPlugin.h>
    #include <maya/MFnNurbsCurve.h>

    #include <maya/MSyntax.h>
    #include <maya/MArgParser.h>
    #include <maya/MArgDatabase.h>
    #include <maya/MCursor.h>

    #if defined(OSMac_MachO_)
    #include <OpenGL/gl.h>
    #include <OpenGL/glu.h>
    #else
    #include <GL/gl.h>
    #include <GL/glu.h>
    #endif

    #define kPitchFlag          "-p"
    #define kPitchFlagLong      "-pitch"
    #define kRadiusFlag         "-r"
    #define kRadiusFlagLong     "-radius"
    #define kNumberCVsFlag      "-ncv"
    #define kNumberCVsFlagLong  "-numCVs"
    #define kUpsideDownFlag     "-ud"
    #define kUpsideDownFlagLong "-upsideDown"

    // The users tool command

    #define     NUMBER_OF_CVS       20

    class helixTool : public MPxToolCommand
    {
    public:
                        helixTool();
        virtual         ~helixTool();
        static void*    creator();

        MStatus         doIt(const MArgList& args);
        MStatus         parseArgs(const MArgList& args);
        MStatus         redoIt();
        MStatus         undoIt();
        bool            isUndoable() const;
        MStatus         finalize();
        static MSyntax  newSyntax();

        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;         // Helix upsideDown
        MDagPath        path;           // The dag path to the curve.
                                        // Don't save the pointer!
        MDagModifier    modifier;
        bool            modifierCreated;
    };

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

    helixTool::~helixTool() {}

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

    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)
    //
    // Description
    //     Sets up the helix parameters from arguments passed to the
    //     MEL command.
    //
    {
        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;
    }

    MStatus helixTool::redoIt()
    //
    // Description
    //     This method creates the helix curve from the
    //     pitch and radius values
    //
    {
        MStatus status;
        if (modifierCreated) {
            status = modifier.undoIt();
        }
        else {
            const unsigned  deg     = 3;            // Curve Degree
            const unsigned  ncvs    = numCV;        // 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;

            curveFn.create(controlVertices, knotSequences, deg,
                MFnNurbsCurve::kOpen, false, false,
                MObject::kNullObj, &status);

            if (status) {
                status = curveFn.getPath(path);
            }
            else {
                status.perror("Error creating curve");
            }
        }

        return status;
    }

    MStatus helixTool::undoIt()
    //
    // Description
    //     Removes the helix curve from the model.
    //
    {
        if (!modifierCreated) {
            modifier.deleteNode(path.transform());
            modifierCreated = true;
        }
        return modifier.doIt();
    }

    bool helixTool::isUndoable() const
    //
    // Description
    //     Set this command to be undoable.
    //
    {
        return true;
    }

    MStatus helixTool::finalize()
    //
    // Description
    //     Command is finished, construct a string for the command
    //     for journaling.
    //
    {
        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 );
    }

    void helixTool::setRadius(double newRadius)
    {
        radius = newRadius;
    }

    void helixTool::setPitch(double newPitch)
    {
        pitch = newPitch;
    }

    void helixTool::setNumCVs(unsigned newNumCVs)
    {
        numCV = newNumCVs;
    }

    void helixTool::setUpsideDown(bool newUpsideDown)
    {
        upDown = newUpsideDown;
    }

    //
    // The user Context
    //
    //   Contexts give the user the ability to write functions
    //   for handling events.
    //
    //   Contexts aren't registered in the plugin, instead a
    //   command class (MPxContextCommand) is registered and is used
    //   to create instances of the context.
    //

    const char helpString[] = "Click and drag to draw helix";

    class helixContext : public MPxContext
    {
    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);

        virtual void    getClassName(MString & name) const;

        void            setNumCVs(unsigned newNumCVs);
        void            setUpsideDown(bool newUpsideDown);
        unsigned        numCVs();
        bool            upsideDown();

    private:
        void            drawGuide();

        bool            firstDraw;
        short           startPos_x, startPos_y;
        short           endPos_x, endPos_y;
        unsigned        numCV;
        bool            upDown;
        M3dView         view;
        GLdouble        height,radius;

    };

    helixContext::helixContext()
    {
        numCV = 20;
        upDown = false;
        setTitleString("Helix Tool");

        setCursor( MCursor::defaultCursor );

        // Tell the context which XPM to use so the tool can properly
        // be a candidate for the 6th position on the mini-bar.
        setImage("helixTool.xpm", MPxContext::kImage1 );
    }

    void helixContext::toolOnSetup(MEvent &)
    {
        setHelpString(helpString);
    }

    MStatus helixContext::doPress(MEvent &event)
    {
        event.getPosition(startPos_x, startPos_y);
        view = M3dView::active3dView();
        firstDraw = true;
        return MS::kSuccess;
    }

    void helixContext::drawGuide()
    {
        int upFactor;
        if (upDown) upFactor = 1;
        else upFactor = -1;

        // Draw the guide cylinder
        //
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
            glRotatef(upFactor*90.0f, 1.0f, 0.0f, 0.0f);
            GLUquadricObj *qobj = gluNewQuadric();
            gluQuadricDrawStyle(qobj, GLU_LINE);
            GLdouble factor = (GLdouble)numCV;
            radius = double(abs(endPos_x - startPos_x))/factor + 0.1;
            height = double(abs(endPos_y - startPos_y))/factor + 0.1;
            gluCylinder( qobj, radius, radius, height, 8, 1 );
        glPopMatrix();
    }

    MStatus helixContext::doDrag(MEvent & event)
    {
        view.beginXorDrawing(false);

        if (!firstDraw) {
            //  Clear the guide from the old position.
            drawGuide();
        } else {
            firstDraw = false;
        }

        event.getPosition(endPos_x, endPos_y);

        //  Draw the guide at the new position.
        drawGuide();

        view.endXorDrawing();

        return MS::kSuccess;
    }

    MStatus helixContext::doRelease( MEvent & )
    {
        //  Clear the guide from its last position.
        if (!firstDraw) {
            view.beginXorDrawing(false);
            drawGuide();
            view.endXorDrawing();
        }

        helixTool * cmd = (helixTool*)newToolCommand();
        cmd->setPitch( height/numCV );
        cmd->setRadius( radius );
        cmd->setNumCVs( numCV );
        cmd->setUpsideDown( upDown );
        cmd->redoIt();
        cmd->finalize();
        return MS::kSuccess;
    }

    MStatus helixContext::doEnterRegion( MEvent & )
    {
        return setHelpString( helpString );
    }

    void helixContext::getClassName( MString & name ) const
    {
        name.set("helix");
    }

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

    //
    // Context creation command
    //
    //  This is the command that will be used to create instances
    //  of our 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()
    //
    // Description
    //    When the context command is executed in maya, this method
    //    be used to create a context.
    //
    {
        fHelixContext = new helixContext();
        return fHelixContext;
    }

    void* helixContextCmd::creator()
    //
    // Description
    //    This method creates the context command.
    //
    {
        return new helixContextCmd;
    }

    MStatus helixContextCmd::doEditFlags()
    {
        MStatus status = MS::kSuccess;

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

    //
    // The following routines are used to register/unregister
    // the commands we are creating within Maya
    //
    MStatus initializePlugin( MObject obj )
    {
        MStatus status;
        MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.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;
    }

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