meshOpCmd/polyModifierCmd.cpp
 
 
 
meshOpCmd/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;
}