splitUVCmd/polyModifierCmd.cpp

splitUVCmd/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.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.setData( value[0], value[1], value[2] );
object = numDataFn.object();
return MS::kSuccess;
}