moveNumericTool.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.
// ==========================================================================
//+

// 
// moveNumericTool.cc
// 
// Description:
//    Interactive tool for moving an object.
//    This tool also accepts numeric input from the numeric input field.
//
//    This plug-in will register the following two commands in Maya:
//       moveNumericToolCmd <x> <y> <z>
//       moveNumericToolContext
// 
#include <maya/MIOStream.h>
#include <stdio.h>
#include <stdlib.h>

#include <maya/MPxToolCommand.h>
#include <maya/MFnPlugin.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MItSelectionList.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MDagPath.h>

#include <maya/MFnTransform.h>
#include <maya/MItCurveCV.h>
#include <maya/MItSurfaceCV.h>
#include <maya/MItMeshVertex.h>

#include <maya/MPxSelectionContext.h>
#include <maya/MPxContextCommand.h>
#include <maya/M3dView.h>
#include <maya/MFnCamera.h>

#include <maya/MFeedbackLine.h>

#define CHECKRESULT(stat,msg)     \
    if ( MS::kSuccess != stat ) { \
        cerr << msg << endl;      \
    }

#define kVectorEpsilon 1.0e-3

//
// The move command
//
// - this is a tool command which can be used in tool
//   contexts or in the MEL command window.
//
#define     MOVENAME    "moveNumericToolCmd"
#define     DOIT        0
#define     UNDOIT      1
#define     REDOIT      2

class moveCmd : public MPxToolCommand
{
public:
    moveCmd();
    virtual ~moveCmd(); 

    MStatus         doIt( const MArgList& args );
    MStatus         redoIt();
    MStatus         undoIt();
    bool            isUndoable() const;
    MStatus         finalize();
    
public:
    static void*    creator();

    void            setVector( double x, double y, double z );
    static MStatus  getVector( MVector &vec );
private:
    MVector         delta;  // the delta vectors
    MStatus         action( int flag ); // do the work here
};

moveCmd::moveCmd( )
{
    setCommandString( MOVENAME );
}

moveCmd::~moveCmd()
{}

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

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

void moveCmd::setVector( double x, double y, double z)
{
    delta.x = x;
    delta.y = y;
    delta.z = z;
}

MStatus moveCmd::getVector( MVector &vec )
{
    MStatus stat;
    MSelectionList slist;
    MGlobal::getActiveSelectionList( slist );

    MDagPath    mDagPath;
    MObject     mComponent;
    MSpace::Space spc = MSpace::kWorld;

    // Translate first selected object
    //
    if (slist.length() > 0)
        stat = slist.getDagPath(slist.length()-1, mDagPath, mComponent);
    else
        return MS::kFailure;

    if ( MS::kSuccess != stat ) return MS::kFailure;

    MFnTransform transFn( mDagPath, &stat );
    if ( MS::kSuccess == stat ) {
        vec = transFn.translation( spc, &stat );
    }

    return stat;
}

MStatus moveCmd::finalize()
//
// Description
//     Command is finished, construct a string for the command
//     for journalling.
//
{
    MArgList command;
    command.addArg( commandString() );
    command.addArg( delta.x );
    command.addArg( delta.y );
    command.addArg( delta.z );

    // This call adds the command to the undo queue and sets
    // the journal string for the command.
    //
    return MPxToolCommand::doFinalize( command );
}

MStatus moveCmd::doIt( const MArgList& args )
//
// Description
//      Test MItSelectionList class
//
{
    MStatus stat;
    MVector vector( 1.0, 0.0, 0.0 );    // default delta
    unsigned i = 0;

    switch ( args.length() )     // set arguments to vector
    {
        case 1:
            vector.x = args.asDouble( 0, &stat );
            break;
        case 2:
            vector.x = args.asDouble( 0, &stat );
            vector.y = args.asDouble( 1, &stat );
            break;
        case 3:
            vector = args.asVector(i,3);
            break;
        case 0:
        default:
            break;
    }
    delta = vector;

    return action( DOIT );
}

MStatus moveCmd::undoIt( )
//
// Description
//      Undo last delta translation
//
{
    return action( UNDOIT );
}

MStatus moveCmd::redoIt( )
//
// Description
//      Redo last delta translation
//
{
    return action( REDOIT );
}

MStatus moveCmd::action( int flag )
//
// Description
//      Do the actual work here to move the objects by vector
//
{
    MStatus stat;
    MVector vector = delta;

    switch( flag )
    {
        case UNDOIT:    // undo
            vector.x = -vector.x;
            vector.y = -vector.y;
            vector.z = -vector.z;
            break;
        case REDOIT:    // redo
            break;
        case DOIT:      // do command
            break;
        default:
            break;
    }

    // Create a selection list iterator
    //
    MSelectionList slist;
    MGlobal::getActiveSelectionList( slist );

    MDagPath    mdagPath;       // Item dag path
    MObject     mComponent;     // Current component
    MSpace::Space spc = MSpace::kWorld;

    // Translate first selected objects
    //
    if (slist.length() > 0)
        stat = slist.getDagPath(slist.length()-1, mdagPath, mComponent);
    else
        return MS::kFailure;

    if ( MS::kSuccess != stat ) return MS::kFailure;

    MFnTransform transFn( mdagPath, &stat );
    if ( MS::kSuccess == stat ) {
        stat = transFn.translateBy( vector, spc );
        CHECKRESULT(stat,"Error doing translate on transform");
    }

    return MS::kSuccess;
}


//
// The moveNumericTool Context
//
// - tool contexts are custom event handlers. The selection
//   context class defaults to maya's selection mode and
//   allows you to override press/drag/release events.
//
#define     MOVEHELPSTR        "drag to move selected object"
#define     MOVETITLESTR       "moveNumericTool"
#define     TOP         0
#define     FRONT       1
#define     SIDE        2
#define     PERSP       3

class moveNumericContext : public MPxSelectionContext
{
public:
    moveNumericContext();
    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 bool        processNumericalInput( const MDoubleArray &values,
                                               const MIntArray &flags,
                                               bool isAbsolute );
    virtual bool        feedbackNumericalInput() const;
    virtual MSyntax::MArgType   argTypeNumericalInput( unsigned index ) const;

private:
    int currWin;
    MEvent::MouseButtonType downButton;
    M3dView view;
    short startPos_x, endPos_x, start_x, last_x;
    short startPos_y, endPos_y, start_y, last_y;
    moveCmd * cmd;
};


moveNumericContext::moveNumericContext()
{
    MString str( MOVETITLESTR );
    setTitleString( str );

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

void moveNumericContext::toolOnSetup( MEvent & )
{
    MString str( MOVEHELPSTR );
    setHelpString( str );
}

MStatus moveNumericContext::doPress( MEvent & event )
{
    MStatus stat = MPxSelectionContext::doPress( event );
    MSpace::Space spc = MSpace::kWorld;

    // If we are not in selecting mode (i.e. an object has been selected)
    // then set up for the translation.
    //
    if ( !isSelecting() ) {
        event.getPosition( startPos_x, startPos_y );
        view = M3dView::active3dView();

        MDagPath camera;
        stat = view.getCamera( camera );
        if ( stat != MS::kSuccess ) {
            cerr << "Error: M3dView::getCamera" << endl;
            return stat;
        }
        MFnCamera fnCamera( camera );
        MVector upDir = fnCamera.upDirection( spc );
        MVector rightDir = fnCamera.rightDirection( spc );

        // Determine the camera used in the current view
        //
        if ( fnCamera.isOrtho() ) {
            if ( upDir.isEquivalent(MVector::zNegAxis,kVectorEpsilon) ) {
                currWin = TOP;
            } else if (rightDir.isEquivalent(MVector::xAxis,kVectorEpsilon)) {
                currWin = FRONT;
            } else  {
                currWin = SIDE;
            }
        }
        else {
            currWin = PERSP;
        }

        // Create an instance of the move tool command.
        //
        cmd = (moveCmd*)newToolCommand();

        cmd->setVector( 0.0, 0.0, 0.0 );
    }
    feedbackNumericalInput();
    return stat;
}

MStatus moveNumericContext::doDrag( MEvent & event )
{
    MStatus stat;
    stat = MPxSelectionContext::doDrag( event );

    // If we are not in selecting mode (i.e. an object has been selected)
    // then do the translation.
    //
    if ( !isSelecting() ) {
        event.getPosition( endPos_x, endPos_y );
        MPoint endW, startW;
        MVector vec;
        view.viewToWorld( startPos_x, startPos_y, startW, vec );
        view.viewToWorld( endPos_x, endPos_y, endW, vec );
        downButton = event.mouseButton();

        // We reset the the move vector each time a drag event occurs 
        // and then recalculate it based on the start position. 
        //
        cmd->undoIt();

        switch( currWin )
        {
            case TOP:
                switch ( downButton )
                {
                    case MEvent::kMiddleMouse :
                        cmd->setVector( endW.x - startW.x, 0.0, 0.0 );
                        break;
                    case MEvent::kLeftMouse :
                    default:
                        cmd->setVector( endW.x - startW.x, 0.0,
                                        endW.z - startW.z );
                        break;
                }
                break;  

            case FRONT:
                switch ( downButton )
                {
                    case MEvent::kMiddleMouse :
                        cmd->setVector( endW.x - startW.x, 0.0, 0.0 );
                        break;
                    case MEvent::kLeftMouse :
                    default:
                        cmd->setVector( endW.x - startW.x,
                                        endW.y - startW.y, 0.0 );
                        break;
                }
                break;  

            case SIDE:
                switch ( downButton )
                {
                    case MEvent::kMiddleMouse :
                        cmd->setVector( 0.0, 0.0, endW.z - startW.z );
                        break;
                    case MEvent::kLeftMouse :
                    default:
                        cmd->setVector( 0.0, endW.y - startW.y,
                                        endW.z - startW.z );
                        break;
                }
                break;  

            case PERSP:
                break;
        }

        stat = cmd->redoIt();
        view.refresh( true );
    }
    feedbackNumericalInput();
    return stat;
}

MStatus moveNumericContext::doRelease( MEvent & event )
{
    MStatus stat = MPxSelectionContext::doRelease( event );
    if ( !isSelecting() ) {
        event.getPosition( endPos_x, endPos_y );

        // Delete the move command if we have moved less then 2 pixels
        // otherwise call finalize to set up the journal and add the
        // command to the undo queue.
        //
        if (abs(startPos_x - endPos_x) < 2 && abs(startPos_y - endPos_y) < 2) {
            delete cmd;
            view.refresh( true );
        }
        else {
            stat = cmd->finalize();
            view.refresh( true );
        }
    }
    feedbackNumericalInput();
    return stat;
}

MStatus moveNumericContext::doEnterRegion( MEvent & /*event*/ )
//
// Print the tool description in the help line.
//
{
    MString str( MOVEHELPSTR );
    return setHelpString( str );
}


bool moveNumericContext::processNumericalInput ( const MDoubleArray &values,
                                                 const MIntArray &flags,
                                                 bool isAbsolute )
{
    unsigned valueLength = values.length();

    cmd = (moveCmd *) newToolCommand();

    MVector vec;
    /* MStatus stat = */ moveCmd::getVector(vec);

    if (isAbsolute) {
        MVector absoluteDelta;

        if (ignoreEntry(flags, 0) || (valueLength < 1)) absoluteDelta.x = 0;
        else absoluteDelta.x = values[0] - vec.x;

        if (ignoreEntry(flags, 1) || (valueLength < 2)) absoluteDelta.y = 0;
        else absoluteDelta.y = values[1] - vec.y;

        if (ignoreEntry(flags, 2) || (valueLength < 3)) absoluteDelta.z = 0;
        else absoluteDelta.z = values[2] - vec.z;

        cmd->setVector(absoluteDelta.x, absoluteDelta.y, absoluteDelta.z);
    }
    else {
        MVector relativeDelta;

        if (ignoreEntry(flags, 0) || (valueLength < 1)) relativeDelta.x = 0;
        else relativeDelta.x = values[0];

        if (ignoreEntry(flags, 1) || (valueLength < 2)) relativeDelta.y = 0;
        else relativeDelta.y = values[1];

        if (ignoreEntry(flags, 2) || (valueLength < 3)) relativeDelta.z = 0;
        else relativeDelta.z = values[2];

        cmd->setVector(relativeDelta.x, relativeDelta.y, relativeDelta.z);
    }

    cmd->redoIt();
    cmd->finalize();

    feedbackNumericalInput();
    return true;
}

bool moveNumericContext::feedbackNumericalInput() const
{
    MFeedbackLine::setTitle("moveNumericTool");
    MFeedbackLine::setFormat("^6.3f ^6.3f ^6.3f");
    MVector vec;
    /* MStatus stat = */ moveCmd::getVector(vec);
    MFeedbackLine::setValue(0, vec.x);
    MFeedbackLine::setValue(1, vec.y);
    MFeedbackLine::setValue(2, vec.z);
    return true;
}

MSyntax::MArgType moveNumericContext::argTypeNumericalInput(unsigned /*index*/) 
const
{
    return MSyntax::kDistance;
}


//
// Context creation command
//
//  This is the command that will be used to create instances
//  of our context.
//
#define     CREATE_CTX_NAME "moveNumericToolContext"

class moveNumericContextCommand : public MPxContextCommand
{
public:
    moveNumericContextCommand() {};
    virtual MPxContext * makeObj();

public:
    static void* creator();
};

MPxContext * moveNumericContextCommand::makeObj()
{
    return new moveNumericContext();
}

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


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

    status = plugin.registerContextCommand(CREATE_CTX_NAME,
                                           &moveNumericContextCommand::creator,
                                           MOVENAME, &moveCmd::creator);
    if (!status) {
        status.perror("registerContextCommand");
        return status;
    }

    return status;
}

MStatus uninitializePlugin( MObject obj )
{
    MStatus status;
    MFnPlugin plugin( obj );

    status = plugin.deregisterContextCommand( CREATE_CTX_NAME, MOVENAME );
    if (!status) {
        status.perror("deregisterContextCommand");
        return status;
    }

    return status;
}