text: //-
  // ==========================================================================
  // 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", true );
  	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", true);
  	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", true);
  	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", true , &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", true);
  
  		// 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", true);
  
  			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", true, &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", true);
  
  		// 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", true, &status );
  		MCheckStatus( status, "Could not retrieve inMesh" );
  		meshNodeOutMeshPlug = depNodeFn.findPlug( "outMesh", true, &status );
  		MCheckStatus( status, "Could not retrieve outMesh" );
  
  		depNodeFn.setObject( dupMeshNodeShape );
  		dupMeshNodeSrcPlug = depNodeFn.findPlug( "outMesh", true, &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", true);
  
  		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", true, &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", true, &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", true, &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;
  }
  