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

// 
// File: polyModifierCmd.cpp
//
// MEL Command: polyModifierCmd
//
// Author: Lonnie Li
//

/* Includes */

#include "polyModifierCmd.h"

// General Includes
//
#include <maya/MGlobal.h>
#include <maya/MFloatVector.h>
#include <maya/MObjectArray.h>
#include <maya/MPlugArray.h>

#include <maya/MIOStream.h>

// Function Sets
//
#include <maya/MFnDependencyNode.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnNumericData.h>


/* Macros */

// MCheckStatus (Debugging tool)
//
#ifdef _DEBUG
#   define MCheckStatus(status,message)         \
        if( MS::kSuccess != status ) {          \
            MString error("Status failed: ");   \
            error += message;                   \
            MGlobal::displayError(error);       \
            return status;                      \
        }
#else
#   define MCheckStatus(status,message)
#endif

// MAssert (Debugging tool)
//
#ifdef _DEBUG
#   define MAssert(state,message)                   \
        if( !state ) {                              \
            MString error("Assertion failed: ");    \
            error += message;                       \
            MGlobal::displayError(error);           \
            return;                                 \
        }
#else
#   define MAssert(state,message)
#endif

// MStatusAssert (Debugging tool)
//
#ifdef _DEBUG
#   define MStatusAssert(state,message)             \
        if( !state ) {                              \
            MString error("Assertion failed: ");    \
            error += message;                       \
            MGlobal::displayError(error);           \
            return MS::kFailure;                    \
        }
#else
#   define MStatusAssert(state,message)
#endif


/* polyModifierCmd Implementation */

polyModifierCmd::polyModifierCmd()
{
    fDagPathInitialized = false;
    fModifierNodeTypeInitialized = false;
    fModifierNodeNameInitialized = false;
}

polyModifierCmd::~polyModifierCmd()
{}

// Protected Methods //

MStatus polyModifierCmd::initModifierNode( MObject /* modifierNode */ )
//
//  Description:
//
//      Override this method in a derived class to set input attributes on the
//      modifier node. If not overidden, the modifier node will remain in it's
//      default state upon construction.
//
//      The argument 'MObject modifierNode', is not used by this base class
//      implementation. However, it may be used by derived classes. To avoid
//      compiler warnings of unreferenced parameters, we comment out the parameter
//      name.
//
{
    return MS::kSuccess;
}

MStatus polyModifierCmd::directModifier( MObject /* mesh */ )
//
//  Description:
//
//      Override this method in a derived class to provide an implementation for
//      directly modifying the mesh (writing on the mesh itself). This method is
//      only called in the case where history does not exist and history is turned
//      off (ie. DG operations are not desirable).
//
//      The argument 'MObject mesh', is not used by this base class implementation.
//      However, it may be used by derived classes. To avoid compiler warnings
//      of unreferenced parameters, we comment out the parameter name.
//
{
    return MS::kSuccess;
}

MStatus polyModifierCmd::doModifyPoly()
{
    MStatus status = MS::kFailure;

    if( isCommandDataValid() )
    {
        // Get the state of the polyMesh
        //
        collectNodeState();

        if( !fHasHistory && !fHasRecordHistory )
        {
            MObject meshNode = fDagPath.node();

            // Pre-process the mesh - Cache old mesh (including tweaks, if applicable)
            //
            cacheMeshData();
            cacheMeshTweaks();

            // Call the directModifier
            //
            status = directModifier( meshNode );
        }
        else
        {
            MObject modifierNode;
            createModifierNode( modifierNode );
            initModifierNode( modifierNode );
            status = connectNodes( modifierNode );
        }
    }

    return status;
}

MStatus polyModifierCmd::redoModifyPoly()
{
    MStatus status = MS::kSuccess;

    if( !fHasHistory && !fHasRecordHistory )
    {
        MObject meshNode = fDagPath.node();

        // Call the directModifier - No need to pre-process the mesh data again
        //                           since we already have it.
        //
        status = directModifier( meshNode );
    }
    else
    {
        // Call the redo on the DG and DAG modifiers
        //
        if( !fHasHistory )
        {
            fDagModifier.doIt();
        }
        status = fDGModifier.doIt();
    }

    return status;
}

MStatus polyModifierCmd::undoModifyPoly()
{
    MStatus status = MS::kSuccess;

    if( !fHasHistory && !fHasRecordHistory )
    {
        status = undoDirectModifier();
    }
    else
    {
        fDGModifier.undoIt();

        // undoCachedMesh must be called before undoTweakProcessing because 
        // undoCachedMesh copies the original mesh *without* tweaks back onto
        // the existing mesh. Any changes done before the copy will be lost.
        //
        if( !fHasHistory )
        {
            status = undoCachedMesh();
            MCheckStatus( status, "undoCachedMesh" );
            fDagModifier.undoIt();
        }

        status = undoTweakProcessing();
        MCheckStatus( status, "undoTweakProcessing" );
    }

    return status;
}

// Private Methods //

bool polyModifierCmd::isCommandDataValid()
{
    bool valid = true;

    // Check the validity of the DAG path
    //
    if( fDagPathInitialized )
    {
        fDagPath.extendToShape();
        if( !fDagPath.isValid() || fDagPath.apiType() != MFn::kMesh )
        {
            valid = false;
        }
    }
    else
    {
        valid = false;
    }

    // Check the validity of the Modifier node type/name
    //
    if( !fModifierNodeTypeInitialized && !fModifierNodeNameInitialized )
    {
        valid = false;
    }

    return valid;
}

void polyModifierCmd::collectNodeState()
{
    MStatus status;

    // Collect node state information on the given polyMeshShape
    //
    // - HasHistory (Construction History exists)
    // - HasTweaks
    // - HasRecordHistory (Construction History is turned on)
    //
    fDagPath.extendToShape();
    MObject meshNodeShape = fDagPath.node();

    MFnDependencyNode depNodeFn;
    depNodeFn.setObject( meshNodeShape );

    MPlug inMeshPlug = depNodeFn.findPlug( "inMesh" );
    fHasHistory = inMeshPlug.isConnected();

    // Tweaks exist only if the multi "pnts" attribute contains plugs
    // which contain non-zero tweak values. Use false, until proven true
    // search algorithm.
    //
    fHasTweaks = false;
    MPlug tweakPlug = depNodeFn.findPlug( "pnts" );
    if( !tweakPlug.isNull() )
    {
        // ASSERT: tweakPlug should be an array plug!
        //
        MAssert( (tweakPlug.isArray()),
                 "tweakPlug.isArray() -- tweakPlug is not an array plug" );

        MPlug tweak;
        MFloatVector tweakData;
        int i;
        int numElements = tweakPlug.numElements();

        for( i = 0; i < numElements; i++ )
        {
            tweak = tweakPlug.elementByPhysicalIndex( i, &status );
            if( status == MS::kSuccess && !tweak.isNull() )
            {
                getFloat3PlugValue( tweak, tweakData );
                if( 0 != tweakData.x ||
                    0 != tweakData.y ||
                    0 != tweakData.z )
                {
                    fHasTweaks = true;
                    break;
                }
            }
        }
    }

    int result;
    MGlobal::executeCommand( "constructionHistory -q -tgl", result );
    fHasRecordHistory = (0 != result);
}

MStatus polyModifierCmd::createModifierNode( MObject& modifierNode )
{
    MStatus status = MS::kFailure;

    if( fModifierNodeTypeInitialized || fModifierNodeNameInitialized )
    {
        if( fModifierNodeTypeInitialized )
        {
            modifierNode = fDGModifier.createNode( fModifierNodeType, &status );
        }
        else if( fModifierNodeNameInitialized )
        {
            modifierNode = fDGModifier.createNode( fModifierNodeName, &status );
        }

        // Check to make sure that we have a modifier node of the appropriate type.
        // Requires an inMesh and outMesh attribute.
        //
        MFnDependencyNode depNodeFn( modifierNode );
        MObject inMeshAttr;
        MObject outMeshAttr;
        inMeshAttr = depNodeFn.attribute( "inMesh" );
        outMeshAttr = depNodeFn.attribute( "outMesh" );

        if( inMeshAttr.isNull() || outMeshAttr.isNull() )
        {
            displayError( "Invalid Modifier Node: inMesh and outMesh attributes are required." );
            status = MS::kFailure;
        }
    }
    
    return status;
}

MStatus polyModifierCmd::processMeshNode( modifyPolyData& data )
{
    MStatus status = MS::kSuccess;

    // Declare our function sets. Use MFnDagNode here so
    // we can retrieve the parent transform.
    //
    MFnDagNode dagNodeFn;

    // Use the DAG path to retrieve our mesh shape node. 
    //
    data.meshNodeShape = fDagPath.node();
    dagNodeFn.setObject( data.meshNodeShape );

    // ASSERT: meshNodeShape node should have a parent transform!
    //
    MStatusAssert( (0 < dagNodeFn.parentCount()),
                   "0 < dagNodeFn.parentCount() -- meshNodeshape has no parent transform" );
    data.meshNodeTransform = dagNodeFn.parent(0);
    
    data.meshNodeDestPlug = dagNodeFn.findPlug( "inMesh" );
    data.meshNodeDestAttr = data.meshNodeDestPlug.attribute();

    return status;
}

MStatus polyModifierCmd::processUpstreamNode( modifyPolyData& data )
{
    MStatus status = MS::kSuccess;

    // Declare our function sets - Although dagNodeFn derives from depNodeFn, we need
    //                             both since dagNodeFn can only refer to DAG objects.
    //                             We will use depNodeFn for all times other when dealing
    //                             with the DAG.
    //
    MFnDependencyNode   depNodeFn;
    MFnDagNode          dagNodeFn;

    // Use the selected node's plug connections to find the upstream plug.
    // Since we are looking at the selected node's inMesh attribute, it will
    // always have only one connection coming in if it has history, and none
    // otherwise.
    //
    // If there is no history, copy the selected node and place it ahead of the
    // modifierNode as the new upstream node so that the modifierNode has an
    // input mesh to operate on.
    //
    MPlugArray tempPlugArray;

    if( fHasHistory )
    {
        // Since we have history, look for what connections exist on the
        // meshNode "inMesh" plug. "inMesh" plugs should only ever have one
        // connection.
        //
        data.meshNodeDestPlug.connectedTo( tempPlugArray, true, false);

        // ASSERT: Only one connection should exist on meshNodeShape.inMesh!
        //
        MStatusAssert( (tempPlugArray.length() == 1),
                       "tempPlugArray.length() == 1 -- 0 or >1 connections on meshNodeShape.inMesh" );
        data.upstreamNodeSrcPlug = tempPlugArray[0];

        // Construction history only deals with shapes, so we can grab the
        // upstreamNodeShape off of the source plug.
        //
        data.upstreamNodeShape = data.upstreamNodeSrcPlug.node();
        depNodeFn.setObject( data.upstreamNodeShape );
        data.upstreamNodeSrcAttr = data.upstreamNodeSrcPlug.attribute();

        // Disconnect the upstream node and the selected node, so we can
        // replace them with our own connections below.
        //
        fDGModifier.disconnect( data.upstreamNodeSrcPlug,
                                data.meshNodeDestPlug );

    }
    else    // No History (!fHasHistory)
    {
        // Use the DAG node function set to duplicate the shape of the meshNode.
        // The duplicate method will return an MObject handle to the transform
        // of the duplicated shape, so traverse the dag to locate the shape. Store
        // this duplicate shape as our "upstream" node to drive the input for the 
        // modifierNode.
        //
        dagNodeFn.setObject( data.meshNodeShape );
        data.upstreamNodeTransform = dagNodeFn.duplicate( false, false );
        dagNodeFn.setObject( data.upstreamNodeTransform );

        // Ensure that our upstreamNode is pointing to a shape.
        //
        MStatusAssert( (0 < dagNodeFn.childCount()),
                       "0 < dagNodeFn.childCount() -- Duplicate meshNode transform has no shape." );
        data.upstreamNodeShape = dagNodeFn.child(0);

        // Re-parent the upstreamNodeShape under our original transform
        //
        status = fDagModifier.reparentNode( data.upstreamNodeShape, data.meshNodeTransform );
        MCheckStatus( status, "reparentNode" );

        // Perform the DAG re-parenting
        // 
        // Note: This reparent must be performed before the deleteNode() is called.
        //       See polyModifierCmd.h (see definition of fDagModifier) for more details.
        //
        status = fDagModifier.doIt();
        MCheckStatus( status, "fDagModifier.doIt()" );

        // Mark the upstreamNodeShape (the original shape) as an intermediate object
        // (making it invisible to the user)
        //
        dagNodeFn.setObject( data.upstreamNodeShape );
        dagNodeFn.setIntermediateObject( true );

        // Get the upstream node source attribute
        //
        data.upstreamNodeSrcAttr = dagNodeFn.attribute( "outMesh" );
        data.upstreamNodeSrcPlug = dagNodeFn.findPlug( "outMesh", &status );

        // Remove the duplicated transform node (clean up)
        //
        status = fDagModifier.deleteNode( data.upstreamNodeTransform );
        MCheckStatus( status, "deleteNode" );

        // Perform the DAG delete node
        //
        // Note: This deleteNode must be performed after the reparentNode() method is
        //       completed. See polyModifierCmd.h (see definition of fDagModifier) for
        //       details.
        //
        status = fDagModifier.doIt();
        MCheckStatus( status, "fDagModifier.doIt()" );

        // Cache the DAG path to the duplicate shape
        //
        dagNodeFn.getPath( fDuplicateDagPath );
    }

    return status;
}

MStatus polyModifierCmd::processModifierNode( MObject modifierNode,
                                              modifyPolyData& data )
{
    MStatus status = MS::kSuccess;

    MFnDependencyNode depNodeFn ( modifierNode );
    data.modifierNodeSrcAttr = depNodeFn.attribute( "outMesh" );
    data.modifierNodeDestAttr = depNodeFn.attribute( "inMesh" );

    return status;
}

MStatus polyModifierCmd::processTweaks( modifyPolyData& data )
{
    MStatus status = MS::kSuccess;

    // Clear tweak undo information (to be rebuilt)
    //
    fTweakIndexArray.clear();
    fTweakVectorArray.clear();

    // Extract the tweaks and place them into a polyTweak node. This polyTweak node
    // will be placed ahead of the modifier node to maintain the order of operations.
    // Special care must be taken into recreating the tweaks:
    //
    //      1) Copy tweak info (including connections!)
    //      2) Remove tweak info from both meshNode and a duplicate meshNode (if applicable)
    //      3) Cache tweak info for undo operations
    //
    if( fHasTweaks )
    {
        // Declare our function sets
        //
        MFnDependencyNode depNodeFn;

        // Declare our attributes and plugs
        //
        MPlug   meshTweakPlug;
        MPlug   upstreamTweakPlug;
        MObject tweakNodeTweakAttr;

        // Declare our tweak processing variables
        //
        MPlug               tweak;
        MPlug               tweakChild;
        MObject             tweakData;
        MObjectArray        tweakDataArray;
        MFloatVector        tweakVector;

        MIntArray           tweakSrcConnectionCountArray;
        MPlugArray          tweakSrcConnectionPlugArray;
        MIntArray           tweakDstConnectionCountArray;
        MPlugArray          tweakDstConnectionPlugArray;

        MPlugArray          tempPlugArray;

        unsigned i;
        unsigned j;
        unsigned k;

        // Create the tweak node and get its attributes
        //
        data.tweakNode = fDGModifier.createNode( "polyTweak" );
        depNodeFn.setObject( data.tweakNode );
        data.tweakNodeSrcAttr = depNodeFn.attribute( "output" );
        data.tweakNodeDestAttr = depNodeFn.attribute( "inputPolymesh" );
        tweakNodeTweakAttr = depNodeFn.attribute( "tweak" );

        depNodeFn.setObject( data.meshNodeShape );
        meshTweakPlug = depNodeFn.findPlug( "pnts" );

        // ASSERT: meshTweakPlug should be an array plug!
        //
        MStatusAssert( (meshTweakPlug.isArray()),
                       "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug" );
        unsigned numElements = meshTweakPlug.numElements();

        // Gather meshTweakPlug data
        //
        for( i = 0; i < numElements; i++ )
        {
            // MPlug::numElements() only returns the number of physical elements
            // in the array plug. Thus we must use elementByPhysical index when using
            // the index i.
            //
            tweak = meshTweakPlug.elementByPhysicalIndex(i);

            // If the method fails, the element is NULL. Only append the index
            // if it is a valid plug.
            //
            if( !tweak.isNull() )
            {
                // Cache the logical index of this element plug
                //
                unsigned logicalIndex = tweak.logicalIndex();

                // Collect tweak data and cache the indices and float vectors
                //
                tweak.getValue( tweakData );
                tweakDataArray.append( tweakData );
                getFloat3PlugValue( tweak, tweakVector );
                fTweakIndexArray.append( logicalIndex );
                fTweakVectorArray.append( tweakVector );

                // Collect tweak connection data
                //
                // Parse down to the deepest level of the plug tree and check
                // for connections - look at the child nodes of the element plugs.
                // If any connections are found, record the connection and disconnect
                // it.
                //

                // ASSERT: The element plug should be compound!
                //
                MStatusAssert( (tweak.isCompound()),
                               "tweak.isCompound() -- Element tweak plug is not compound" );

                unsigned numChildren = tweak.numChildren();
                for( j = 0; j < numChildren; j++ )
                {
                    tweakChild = tweak.child(j);
                    if( tweakChild.isConnected() )
                    {
                        // Get all connections with this plug as source, if they exist
                        //
                        tempPlugArray.clear();
                        if( tweakChild.connectedTo( tempPlugArray, false, true ) )
                        {
                            unsigned numSrcConnections = tempPlugArray.length();
                            tweakSrcConnectionCountArray.append( numSrcConnections );

                            for( k = 0; k < numSrcConnections; k++ )
                            {
                                tweakSrcConnectionPlugArray.append( tempPlugArray[k] );
                                fDGModifier.disconnect( tweakChild, tempPlugArray[k] );
                            }
                        }
                        else
                        {
                            tweakSrcConnectionCountArray.append(0);
                        }

                        // Get the connection with this plug as destination, if it exists
                        //
                        tempPlugArray.clear();
                        if( tweakChild.connectedTo( tempPlugArray, true, false ) )
                        {
                            // ASSERT: tweakChild should only have one connection as destination!
                            //
                            MStatusAssert( (tempPlugArray.length() == 1),
                                           "tempPlugArray.length() == 1 -- 0 or >1 connections on tweakChild" );

                            tweakDstConnectionCountArray.append(1);
                            tweakDstConnectionPlugArray.append( tempPlugArray[0] );
                            fDGModifier.disconnect( tempPlugArray[0], tweakChild );
                        }
                        else
                        {
                            tweakDstConnectionCountArray.append(0);
                        }
                    }
                    else
                    {
                        tweakSrcConnectionCountArray.append(0);
                        tweakDstConnectionCountArray.append(0);
                    }
                }
            }
        }

        // Apply meshTweakPlug data to our polyTweak node
        //
        MPlug polyTweakPlug( data.tweakNode, tweakNodeTweakAttr );
        unsigned numTweaks = fTweakIndexArray.length();
        int srcOffset = 0;
        int dstOffset = 0;

        for( i = 0; i < numTweaks; i++ )
        {
            // Apply tweak data
            //
            tweak = polyTweakPlug.elementByLogicalIndex( fTweakIndexArray[i] );
            tweak.setValue( tweakDataArray[i] );

            // ASSERT: Element plug should be compound!
            //
            MStatusAssert( (tweak.isCompound()),
                           "tweak.isCompound() -- Element plug, 'tweak', is not compound" );

            unsigned numChildren = tweak.numChildren();
            for( j = 0; j < numChildren; j++ )
            {
                tweakChild = tweak.child(j);

                // Apply tweak source connection data
                //
                if( 0 < tweakSrcConnectionCountArray[i*numChildren + j] )
                {
                    for( k = 0;
                         k < (unsigned) tweakSrcConnectionCountArray[i*numChildren + j];
                         k++ )
                    {
                        fDGModifier.connect( tweakChild,
                                             tweakSrcConnectionPlugArray[srcOffset] );
                        srcOffset++;
                    }
                }
                        
                // Apply tweak destination connection data
                //
                if( 0 < tweakDstConnectionCountArray[i*numChildren + j] )
                {
                    fDGModifier.connect( tweakDstConnectionPlugArray[dstOffset],
                                         tweakChild );
                    dstOffset++;
                }
            }
        }

/*      // Now, set the tweak values on the meshNode(s) to zero (History dependent)
        //
        MFnNumericData numDataFn;
        MObject nullVector;

        // Create a NULL vector (0,0,0) using MFnNumericData to pass into the plug
        //
        numDataFn.create( MFnNumericData::k3Float );
        numDataFn.setData( 0, 0, 0 );
        nullVector = numDataFn.object();

        for( i = 0; i < numTweaks; i++ )
        {
            // Access using logical indices since they are the only plugs guaranteed
            // to hold tweak data.
            //
            tweak = meshTweakPlug.elementByLogicalIndex( fTweakIndexArray[i] );
            tweak.setValue( nullVector );
        }

        // Only have to clear the tweaks off the duplicate mesh if we do not have history
        // and we want history.
        //
        if( !fHasHistory && fHasRecordHistory )
        {
            depNodeFn.setObject( data.upstreamNodeShape );
            upstreamTweakPlug = depNodeFn.findPlug( "pnts" );

            if( !upstreamTweakPlug.isNull() )
            {
                for( i = 0; i < numTweaks; i++ )
                {
                    tweak = meshTweakPlug.elementByLogicalIndex( fTweakIndexArray[i] );
                    tweak.setValue( nullVector );
                }
            }
        }
*/  }

    return status;
}

MStatus polyModifierCmd::connectNodes( MObject modifierNode )
//
//  Description:
//
//      This method connects up the modifier nodes, while accounting for DG factors
//      such as construction history and tweaks. The method has a series of steps which
//      it runs through to process nodes under varying circumstances:
//
//      1) Gather meshNode connection data (ie. attributes and plugs)
//
//      2) Gather upstreamNode data - This is history-dependent. If the node has history,
//                                    an actual upstreamNode exists and that is used to
//                                    drive the input of our modifierNode.
//
//                                    Otherwise, if the node does not have history, the
//                                    meshNode is duplicated, set as an intermediate object
//                                    and regarded as our new upstreamNode which will drive
//                                    the input of our modifierNode. The case with history
//                                    already has this duplicate meshNode at the top, driving
//                                    all other history nodes and serving as a reference
//                                    to the "original state" of the node before any
//                                    modifications.
//
//      3) Gather modifierNode connection data
//
//      4) Process tweak data (if it exists) - This is history-dependent. If there is
//                                             history, the tweak data is extracted and deleted
//                                             from the meshNode and encapsulated inside a
//                                             polyTweak node. The polyTweak node is then
//                                             inserted ahead of the modifier node.
//
//                                             If there is no history, the same is done as
//                                             in the history case, except the tweaks are
//                                             deleted from the duplicate meshNode in addition
//                                             to the actual meshNode.
//
//      5) Connect the nodes
//
//      6) Collapse/Bake nodes into the actual meshNode if the meshNode had no previous
//         construction history and construction history recording is turned off.
//         (ie. (!fHasHistory && !fHasRecordHistory) == true )
//
{
    MStatus status;

    // Declare our internal processing data structure (see polyModifierCmd.h for definition)
    //
    modifyPolyData data;

    // Get the mesh node, plugs and attributes
    //
    status = processMeshNode( data );
    MCheckStatus( status, "processMeshNode" );

    // Get upstream node, plugs and attributes
    //
    status = processUpstreamNode( data );
    MCheckStatus( status, "processUpstreamNode" );

    // Get the modifierNode attributes
    //
    status = processModifierNode( modifierNode,
                         data );
    MCheckStatus( status, "processModifierNode" );

    // Process tweaks on the meshNode
    //
    status = processTweaks( data );
    MCheckStatus( status, "processTweaks" );

    // Connect the nodes
    //
    if( fHasTweaks )
    {
        MPlug tweakDestPlug( data.tweakNode, data.tweakNodeDestAttr );
        status = fDGModifier.connect( data.upstreamNodeSrcPlug, tweakDestPlug );
        MCheckStatus( status, "upstream-tweak connect failed" );

        MPlug tweakSrcPlug( data.tweakNode, data.tweakNodeSrcAttr );
        MPlug modifierDestPlug( modifierNode, data.modifierNodeDestAttr );
        status = fDGModifier.connect( tweakSrcPlug, modifierDestPlug );
        MCheckStatus( status, "tweak-modifier connect failed" );
    }
    else
    {
        MPlug modifierDestPlug( modifierNode, data.modifierNodeDestAttr );
        status = fDGModifier.connect( data.upstreamNodeSrcPlug, modifierDestPlug );
        MCheckStatus( status, "upstream-modifier connect failed" );
    }

    MPlug modifierSrcPlug( modifierNode, data.modifierNodeSrcAttr );
    MPlug meshDestAttr( data.meshNodeShape, data.meshNodeDestAttr );
    status = fDGModifier.connect( modifierSrcPlug, meshDestAttr );
    MCheckStatus( status, "modifier-mesh connect failed" );
    
    status = fDGModifier.doIt();

    return status;
}

MStatus polyModifierCmd::cacheMeshData()
{
    MStatus status = MS::kSuccess;

    MFnDependencyNode depNodeFn;
    MFnDagNode dagNodeFn;

    MObject meshNode = fDagPath.node();
    MObject dupMeshNode;
    MPlug dupMeshNodeOutMeshPlug;

    // Duplicate the mesh
    //
    dagNodeFn.setObject( meshNode );
    dupMeshNode = dagNodeFn.duplicate();

    MDagPath dupMeshDagPath;
    MDagPath::getAPathTo( dupMeshNode, dupMeshDagPath );
    dupMeshDagPath.extendToShape();

    depNodeFn.setObject( dupMeshDagPath.node() );
    dupMeshNodeOutMeshPlug = depNodeFn.findPlug( "outMesh", &status );
    MCheckStatus( status, "Could not retrieve outMesh" );

    // Retrieve the meshData
    //
    status = dupMeshNodeOutMeshPlug.getValue( fMeshData );
    MCheckStatus( status, "Could not retrieve meshData" );

    // Delete the duplicated node
    //
    MGlobal::deleteNode( dupMeshNode );

    return status;
}

MStatus polyModifierCmd::cacheMeshTweaks()
{
    MStatus status = MS::kSuccess;

    // Clear tweak undo information (to be rebuilt)
    //
    fTweakIndexArray.clear();
    fTweakVectorArray.clear();

    // Extract the tweaks and store them in our local tweak cache members
    //
    if( fHasTweaks )
    {
        // Declare our function sets
        //
        MFnDependencyNode depNodeFn;

        MObject meshNode = fDagPath.node();
        MPlug   meshTweakPlug;

        // Declare our tweak processing variables
        //
        MPlug               tweak;
        MPlug               tweakChild;
        MObject             tweakData;
        MObjectArray        tweakDataArray;
        MFloatVector        tweakVector;

        MPlugArray          tempPlugArray;

        unsigned i;

        depNodeFn.setObject( meshNode );
        meshTweakPlug = depNodeFn.findPlug( "pnts" );

        // ASSERT: meshTweakPlug should be an array plug!
        //
        MStatusAssert( (meshTweakPlug.isArray()),
                       "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug" );
        unsigned numElements = meshTweakPlug.numElements();

        // Gather meshTweakPlug data
        //
        for( i = 0; i < numElements; i++ )
        {
            // MPlug::numElements() only returns the number of physical elements
            // in the array plug. Thus we must use elementByPhysical index when using
            // the index i.
            //
            tweak = meshTweakPlug.elementByPhysicalIndex(i);

            // If the method fails, the element is NULL. Only append the index
            // if it is a valid plug.
            //
            if( !tweak.isNull() )
            {
                // Cache the logical index of this element plug
                //
                unsigned logicalIndex = tweak.logicalIndex();

                // Collect tweak data and cache the indices and float vectors
                //
                getFloat3PlugValue( tweak, tweakVector );
                fTweakIndexArray.append( logicalIndex );
                fTweakVectorArray.append( tweakVector );
            }
        }
    }

    return status;
}

MStatus polyModifierCmd::undoCachedMesh()
{
    MStatus status;

    // Only need to restore the cached mesh if there was no history. Also
    // check to make sure that we are in the record history state.
    //
    MStatusAssert( (fHasRecordHistory), "fHasRecordHistory == true" );

    if( !fHasHistory )
    {
        MFnDependencyNode depNodeFn;

        MString meshNodeName;
        MObject meshNodeShape;
        MPlug   meshNodeDestPlug;
        MPlug   meshNodeOutMeshPlug;
        MObject dupMeshNodeShape;
        MPlug   dupMeshNodeSrcPlug;

        meshNodeShape = fDagPath.node();
        dupMeshNodeShape = fDuplicateDagPath.node();

        depNodeFn.setObject( meshNodeShape );
        meshNodeName = depNodeFn.name();
        meshNodeDestPlug = depNodeFn.findPlug( "inMesh", &status );
        MCheckStatus( status, "Could not retrieve inMesh" );
        meshNodeOutMeshPlug = depNodeFn.findPlug( "outMesh", &status );
        MCheckStatus( status, "Could not retrieve outMesh" );

        depNodeFn.setObject( dupMeshNodeShape );
        dupMeshNodeSrcPlug = depNodeFn.findPlug( "outMesh", &status );
        MCheckStatus( status, "Could not retrieve outMesh" );

        // For the case with tweaks, we cannot write the mesh directly back onto
        // the cachedInMesh, since the shape can have out of date information from the
        // cachedInMesh, thus we temporarily connect the duplicate mesh shape to the
        // mesh shape and force a DG evaluation.
        //
        // For the case without tweaks, we can simply write onto the outMesh, since
        // the shape relies solely on an outMesh when there is no history nor tweaks.
        //
        if( fHasTweaks )
        {
            MDGModifier dgModifier;
            dgModifier.connect( dupMeshNodeSrcPlug, meshNodeDestPlug );
            status = dgModifier.doIt();
            MCheckStatus( status, "Could not connect dupMeshNode -> meshNode" );

            // Need to force a DG evaluation now that the input has been changed.
            //
            MString cmd( "dgeval -src " );
            cmd += meshNodeName;
            cmd += ".inMesh";
            status = MGlobal::executeCommand( cmd, false, false );
            MCheckStatus( status, "Could not force DG eval" );

            // Disconnect the duplicate meshNode now
            //
            dgModifier.undoIt();
        }
        else
        {
            MObject meshData;
            status = dupMeshNodeSrcPlug.getValue( meshData );
            MCheckStatus( status, "Could not retrieve meshData" );
            status = meshNodeOutMeshPlug.setValue( meshData );
            MCheckStatus( status, "Could not set outMesh" );
        }
    }

    return status;
}

MStatus polyModifierCmd::undoTweakProcessing()
{
    MStatus status = MS::kSuccess;

    if( fHasTweaks )
    {
        MFnDependencyNode depNodeFn;

        MObject meshNodeShape;
        MPlug   meshTweakPlug;
        MPlug   tweak;
        MObject tweakData;

        meshNodeShape = fDagPath.node();
        depNodeFn.setObject( meshNodeShape );
        meshTweakPlug = depNodeFn.findPlug( "pnts" );

        MStatusAssert( (meshTweakPlug.isArray()),
                       "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug" );

        unsigned i;
        unsigned numElements = fTweakIndexArray.length();

        for( i = 0; i < numElements; i++ )
        {
            tweak = meshTweakPlug.elementByLogicalIndex( fTweakIndexArray[i] );
            getFloat3asMObject( fTweakVectorArray[i], tweakData );
            tweak.setValue( tweakData );
        }

        // In the case of no history, the duplicate node shape will be disconnected on undo
        // so, there is no need to undo the tweak processing on it.
        //
    }

    return status;
}

MStatus polyModifierCmd::undoDirectModifier()
{
    MStatus status;

    MFnDependencyNode depNodeFn;
    MFnDagNode dagNodeFn;

    MObject meshNode = fDagPath.node();
    depNodeFn.setObject( meshNode );
    
    // For the case with tweaks, we cannot write the mesh directly back onto
    // the cachedInMesh, since the shape can have out of date information from the
    // cachedInMesh. Thus we temporarily create an duplicate mesh, place our
    // old mesh on the outMesh attribute of our duplicate mesh, connect the
    // duplicate mesh shape to the mesh shape, and force a DG evaluation.
    //
    // For the case without tweaks, we can simply write onto the outMesh, since
    // the shape relies solely on an outMesh when there is no history nor tweaks.
    //
    if( fHasTweaks )
    {
        // Retrieve the inMesh and name of our mesh node (for the DG eval)
        //
        depNodeFn.setObject( meshNode );
        MPlug meshNodeInMeshPlug = depNodeFn.findPlug( "inMesh", &status );
        MCheckStatus( status, "Could not retrieve inMesh" );
        MString meshNodeName = depNodeFn.name();

        // Duplicate our current mesh
        //
        dagNodeFn.setObject( meshNode );
        MObject dupMeshNode = dagNodeFn.duplicate();

        // The dagNodeFn::duplicate() returns a transform, but we need a shape
        // so retrieve the DAG path and extend it to the shape.
        //
        MDagPath dupMeshDagPath;
        MDagPath::getAPathTo( dupMeshNode, dupMeshDagPath );
        dupMeshDagPath.extendToShape();

        // Retrieve the outMesh of the duplicate mesh and set our mesh data back
        // on it.
        //
        depNodeFn.setObject( dupMeshDagPath.node() );
        MPlug dupMeshNodeOutMeshPlug = depNodeFn.findPlug( "outMesh", &status );
        MCheckStatus( status, "Could not retrieve outMesh" );
        status = dupMeshNodeOutMeshPlug.setValue( fMeshData );

        // Temporarily connect the duplicate mesh node to our mesh node
        //
        MDGModifier dgModifier;
        dgModifier.connect( dupMeshNodeOutMeshPlug, meshNodeInMeshPlug );
        status = dgModifier.doIt();
        MCheckStatus( status, "Could not connect dupMeshNode -> meshNode" );

        // Need to force a DG evaluation now that the input has been changed.
        //
        MString cmd("dgeval -src ");
        cmd += meshNodeName;
        cmd += ".inMesh";
        status = MGlobal::executeCommand( cmd, false, false );
        MCheckStatus( status, "Could not force DG eval" );

        // Disconnect and delete the duplicate mesh node now
        //
        dgModifier.undoIt();
        MGlobal::deleteNode( dupMeshNode );

        // Restore the tweaks on the mesh
        //
        status = undoTweakProcessing();
    }
    else
    {
        // Restore the original mesh by writing the old mesh data (fMeshData) back
        // onto the outMesh of our meshNode
        //
        depNodeFn.setObject( meshNode );
        MPlug meshNodeOutMeshPlug = depNodeFn.findPlug( "outMesh", &status );
        MCheckStatus( status, "Could not retrieve outMesh" );
        status = meshNodeOutMeshPlug.setValue( fMeshData );
        MCheckStatus( status, "Could not set meshData" );
    }

    return status;
}

MStatus polyModifierCmd::getFloat3PlugValue( MPlug plug, MFloatVector & value )
{
    // Retrieve the value as an MObject
    //
    MObject object;
    plug.getValue( object );

    // Convert the MObject to a float3
    //
    MFnNumericData numDataFn( object );
    numDataFn.getData( value[0], value[1], value[2] );
    return MS::kSuccess;
}

MStatus polyModifierCmd::getFloat3asMObject( MFloatVector value, MObject& object )
{
    // Convert the float value into an MObject
    //
    MFnNumericData numDataFn;
    numDataFn.create( MFnNumericData::k3Float );
    numDataFn.setData( value[0], value[1], value[2] );
    object = numDataFn.object();
    return MS::kSuccess;
}