polyModifierCmd のソース コードは、devkit/plug-ins ディレクトリにあります。
polyModifierCmd には、ポリゴン メッシュを修正するコマンドを作成する一般プロセスと Maya アーキテクチャとインタフェースがカプセル化されています。完全にポリゴンを処理しますが、DG 概念で結合される、Maya のその他のオブジェクト タイプに拡張できます。
すべてのポリゴン修正コマンドに共通していますが、プロセスのこの部分には、コンストラクション ヒストリ、ツィーク、polyShape ノードに関連する、Maya アーキテクチャとのすべてのやり取りが含まれます。
これは単一アクションなので、polyShape ノードに修正を適用するには、doIt()、undoIt()、redoIt()をpolyShape ノード内に実装することもできます。polyModifierCmd は MPxCommand のサブセットなので、派生した polyModifierCmd クラスがすべてフル コマンド アーキテクチャを継承するように、polyModifierCmd 自身を MPxCommand から派生させることができます。このため、doIt() メソッド、undoIt() メソッド、redoIt() メソッドは、操作を実行するために実際のコマンド クラスで必要になるので、オーバーライドすることはできません。その代わりに、doModifyPoly()、undoModifyPoly()、redoModifyPoly() という、類似メソッドの独自のグループを定義します。
派生クラスでは、polyModifierCmd を初期化して doIt() 内で doModifyPoly() を呼び出し、元に戻すとやり直しの機能を拡張できます。 splitUVCmd の例は、派生した polyModifierCmd コマンドの実装例です。以下は、polyModifierCmd のクラス インタフェースです。
class polyModifierCmd : MPxCommand
{
public:
polyModifierCmd();
virtual ~polyModifierCmd();
// Restrict access to derived classes only
//
protected:
////////////////////////////////////
// polyModifierCmd Initialization //
////////////////////////////////////
// Target polyMesh to modify
//
void setMeshNode( MDagPath mesh );
MDagPath getMeshNode() const;
// Modifier node type
//
void setModifierNodeType( MTypeId type );
void setModifierNodeName( MString name );
MTypeId getModifierNodeType() const;
MString getModifierNodeName() const;
///////////////////////////////
// polyModifierCmd Execution //
///////////////////////////////
virtual MStatus initModifierNode( MObject modifierNode );
virtual MStatus directModifier( MObject mesh );
MStatus doModifyPoly();
MStatus redoModifyPoly();
MStatus undoModifyPoly();
private:
//////////////////////////////////////////////
// polyModifierCmd Internal Processing Data //
//////////////////////////////////////////////
struct modifyPolyData
{
MObject meshNodeTransform;
MObject meshNodeShape;
MPlug meshNodeDestPlug;
MObject meshNodeDestAttr;
MObject upstreamNodeTransform;
MObject upstreamNodeShape;
MPlug upstreamNodeSrcPlug;
MObject upstreamNodeSrcAttr;
MObject modifierNodeSrcAttr;
MObject modifierNodeDestAttr;
MObject tweakNode;
MObject tweakNodeSrcAttr;
MObject tweakNodeDestAttr;
};
//////////////////////////////////////
// polyModifierCmd Internal Methods //
//////////////////////////////////////
// Preprocessing methods
//
bool isCommandDataValid();
void collectNodeState();
// Modifier node methods
//
MStatus createModifierNode( MObject& modifierNode );
// Node processing methods (need to be executed in this order)
//
MStatus processMeshNode( modifyPolyData& data );
MStatus processUpstreamNode( modifyPolyData& data );
MStatus processModifierNode( MObject modifierNode,
modifyPolyData& data );
MStatus processTweaks( modifyPolyData& data );
// Node connection methods
//
MStatus connectNodes( MObject modifierNode );
// Mesh caching methods
//
MStatus cacheMeshData();
MStatus cacheMeshTweaks();
// Undo methods
//
MStatus undoCachedMesh();
MStatus undoTweakProcessing();
MStatus undoDirectModifier();
/////////////////////////////////////
// polyModifierCmd Utility Methods //
/////////////////////////////////////
MStatus getFloat3PlugValue( MPlug plug,
MFloatVector& value );
MStatus getFloat3asMObject( MFloatVector value,
MObject& object );
//////////////////////////
// polyModifierCmd Data //
//////////////////////////
// polyMesh
//
bool fDagPathInitialized;
MDagPath fDagPath;
MDagPath fDuplicateDagPath;
// Modifier node type
//
bool fModifierNodeTypeInitialized;
bool fModifierNodeNameInitialized;
MTypeId fModifierNodeType;
MString fModifierNodeName;
// Node State Information
//
bool fHasHistory;
bool fHasTweaks;
bool fHasRecordHistory;
// Cached Tweak Data
//
MIntArray fTweakIndexArray;
MFloatVectorArray fTweakVectorArray;
// Cached Mesh Data
//
MObject fMeshData;
// DG and DAG Modifier
//
MDGModifier fDGModifier;
MDagModifier fDagModifier;
};
// polyModifierCmd Execution // というラベルが付いたクラス インタフェースのセクションでは、対応する doModifyPoly() メソッド、undoModifyPoly() メソッド、redoModifyPoly() メソッドの定義に注意してください。この 3 つのメソッドは、派生コマンドがインタフェースを取るインタフェースの中核を表します。
polyModifierCmd には、以下の 3 つの基本ステージがあります。
詳細については、 polyModifierCmd 初期化を参照してください。
詳細については、 polyModifierCmd 前処理を参照してください。
詳細については、 polyModifierCmd の処理を参照してください。
しかし最も重要なことは、polyModipfierCmd(doModifyPoly(), undoModifyPoly(), redoModifyPoly())の基本クラス インタフェースを理解することです。
モディファイアを polyShape ノードに適用する前に、polyModifierCmd では、プロセスのガイドをする初期化データが必要です。これは、前処理データとは異なります。このデータは処理を開始するために必要な入力であるのに対し、前処理データは初期データから抽出されたデータです。操作の実行には、2 つの初期化データが必要です。このデータから、その他のすべてのデータが抽出されます。
メッシュ データに加える実際の修正を、その適用方法にかかわらず「モディファイア」と呼びます。たとえばコンストラクション ヒストリが存在する場合のモディファイアは、モディファイア ノードとして適用されます。
polyShape ノードは、DAG パスの形式で格納できます。DAG パスは絶対的で、適切なオブジェクトを指すことが保証されますが、MObject は Maya が所有するオブジェクトのハンドルで、プラグインのコール中に変化することがあるので、MObject ではなく DAG パスを使用してください。polyShape 入力は、以下のようにクラス インタフェースで表現されます。
// Prototypes
//
void setMeshNode( MDagPath mesh );
MDagPath getMeshNode() const;
MDagPath fDagPath;
// Implementations
//
void polyModifierCmd::setMeshNode( MDagPath mesh )
{
fDagPath = mesh;
}
MDagPath polyModifierCmd::getMeshNode() const
{
return fDagPath;
}
モディファイア ノードを通した修正の適用には、作成して polyShape に接続できる DG ノード タイプが必要です。これにより、作業する明確なものが提供され、以下のように、ノード タイプかノード名で polyModifierCmd を初期化するインタフェースが提供されます。
// Prototypes
//
void setModifierNodeType( MTypeId type );
void setModifierNodeName( MString name );
MTypeId getModifierNodeType() const;
MString getModifierNodeName() const;
virtual MStatus initModifierNode( MObject modifierNode );
bool fModifierNodeTypeInitialized;
bool fModifierNodeNameInitialized;
MTypeId fModifierNodeType;
MString fModifierNodeName;
// Implementations
//
void polyModifierCmd::setModifierNodeType( MTypeId type )
{
fModifierNodeType = type;
fModifierNodeTypeInitialized = true;
}
void polyModifierCmd::setModifierNodeName( MString name )
{
fModifierNodeName = name;
fModifierNodeNameInitialized = true;
}
MTypeId polyModifierCmd::getModifierNodeType() const
{
return fModifierNodeType;
}
MString polyModifierCmd::getModifierNodeName() const
{
return fModifierNodeName;
}
MStatus polyModifierCmd::initModifierNode( MObject modifierNode )
{
return MS::kSuccess;
}
initModifierNode() メソッドは、作成されるノード タイプでは何の役割も果たしませんが、ノードの作成時に役割を果たします。多くの場合、モディファイア ノードでは、ノードにデータの修正方法を指示する入力が必要です。たとえば splitUVNode では、分割する UV のリストが必要です。ここでは、polyModifierCmd がノードを作成した場合に、polyModifierCmd 側のコマンドがモディファイア ノードのその他のデータをどのように初期化するか、という問題が発生します。polyModifierCmd では初期化できません。polyModifierCmd はモディファイアに関係なく、接続方法のみを認識しているからです。この問題を解決するため、仮想メソッドという形式でコールバックが提供されています。この仮想メソッドによりコマンドがモディファイア ノードにオーバーライドし、またそれがモディファイア ノードの使用前に実行されることが期待されます。
モディファイア ノードとは対照的に、直接的な方法で修正を適用する場合は、格納と適用を行う明確なものが提供されません。つまり、モディファイア ノードにはオブジェクト内部に修正が含まれているため、修正と polyShape ノードが分離していますが、ダイレクト モディファイアではそうではありません。ダイレクト モディファイアは polyShape ノードに直接作用して polyModifierCmd は修正プロセスから独立している必要があるので、派生コマンドのコールバックが提供されます。このコールバックは、ダイレクト モディファイアが必要な場合に実行されます。つまりコンストラクション ヒストリが存在せず、コンストラクション ヒストリがオフになっている場合に実行されます。以下の directModifier コールバックのコードは、仮想メソッドという形式で存在し、polyModifierCmd が適切であるとみなした場合に呼び出されます。
// Prototypes
//
virtual MStatus directModifier( MObject mesh );
// Implementations
//
MStatus polyModifierCmd::directModifier( MObject /* mesh */ )
{
return MS::kSuccess;
}
データを初期化したら、修正の適用に必要な残りのデータを抽出できます。初期化データの他に、以下も把握しておく必要があります。
上記の最初の項目では、与えられた(初期化)データで処理の連続実行が可能かどうかをチェックします。ここでは、polyShape ノード情報とモディファイア ノード情報の初期化をチェックします。データが無効である場合、polyModifierCmd は実行を継続できず、エラーを返します。
// Prototypes
//
bool isCommandDataValid()
// Implementations
//
bool polyModifierCmd::isCommandDataValid()
{
bool valid = true;
// Check the validity of the DAG path
//
if( fDagPathInitialized )
{
// Ensure we are pointing to a shape node
//
fDagPath.extendToShape();
if( !fDagPath.isValid() || fDagPath.apiType != MFn::kMesh )
{
valid = false;
}
}
else
{
valid = false;
}
// Check the validity of the modifier node type/name.
// Can only check that it has been set.
//
if( !fModifierNodeTypeInitialized &&
!fModifierNodeNameInitialized )
{
valid = false;
}
}
後の 3 つの項目は、polyShape ノードのノード状態に関連します。この 3 つのデータでは、データの抽出には polyShape ノードが必要なので、polyShape ノードの妥当性が必要です。データ抽出は直接的で、1 つのメソッド collectNodeState にまとめられています。
// Prototypes
//
void collectNodeState();
// Implementations
//
void polyModifierCmd::collectNodeState()
{
MStatus status;
// Collect the node state information on the given mesh
//
// -HasHistory
// -HasTweaks
// -HasRecordHistory
//
fDagPath.extendToShape();
MObject meshNodeShape = fDagPath.node();
MFnDependencyNode depNodeFn( meshNodeShape );
// If the inMesh is connected, we have history
//
MPlug inMeshPlug = depNodeFn.findPlug( “inMesh” );
fHasHistory = inMeshPlug.isConnected();
// Tweaks exist only if the multi “pnts” attribute contains
// plugs that contain non-zero tweak values. Use false,
// until proven true search pattern.
//
fHasTweaks = false;
MPlug tweakPlug = depNodeFn.findPlug( “pnts” );
if( !tweakPlug.isNull() )
{
// ASSERT : tweakPlug should be an array plug
//
MAssert( tweakPlug.isArray(), “tweakPlug.isArray()” );
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() )
{
// Retrieve the float vector from the plug
//
getFloat3PlugValue( tweak, tweakData );
if( 0 != tweakData.x ||
0 != tweakData.y ||
0 != tweakData.z )
{
fHasTweaks = true;
break;
}
}
}
}
// Query the constructionHistory command for the preference
// of recording history
//
int result;
MGlobal::executeCommand( “constructionHistory –q –tgl”,
result );
fHasRecordHistory = (0 != result );
}
doModifyPoly() は最も複雑で、polyModifierCmd の中核といえるものです。ここではすべてのデータが分析され、適切な処理が実行されます。doModifyPoly() では、プロセス全体を 1 つのメソッドで実装するのではなく、ノード状態を迅速にスキャンして、状態に基づいて適切なメソッドに制御を渡します。
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 the old mesh
//
cacheMeshData();
cacheMeshTweaks();
// Call the directModifier
//
status = directModifier( meshNode );
}
else
{
MObject modifierNode;
createModifierNode( modifierNode );
initModifierNode( modifierNode );
connectNodes( modifierNode );
}
}
}
doModifyPoly() のスケルトン(プログラム)では、ある状態での polyShape ノードとのインタフェースを取る方法の概念がわかります。コンストラクション ヒストリが存在せず、コンストラクション ヒストリの記録がオフになっている場合、元に戻す操作に備えてメッシュ データがキャッシュされ( undoModifyPoly()参照)、directModifier コールバックが呼び出し処理します。その他の場合は modifierNode を介する方法が取られます。呼び出されると、モディファイア ノードが作成されてコールバックによって初期化され、ノード接続のために渡されます。
doModifyPoly() プロセスでは、コンストラクション ヒストリの状態のみが処理されることに注意してください。一連の処理の中ではツィークが大きな役割を果たしますが、ツィークはコンストラクション ヒストリから独立しているので、このプロセス以降の別プロセスでは別個に考慮されます。以下の表は、ノードのコンストラクション ヒストリの状態に基づいて何が呼ばれるかを示しています。
ヒストリ記録: オン | ヒストリ記録: オフ | |
---|---|---|
ヒストリ: あり |
connectNodes() |
connectNodes() |
ヒストリ: なし |
connectNodes() |
directModifier() |
directModifier のコードは、コマンドによって実装されており、注意すべき複雑な側面はありません。コマンドはメッシュ ノードに渡され、そこで直接動作します。すべての制御は、コマンドに残ります。
これとは対照的に、コンストラクション ヒストリに関するその他 3 つのケースはより複雑です。modifierNode の作成から説明します。
MStatus polyModifierCmd::createModifierNode( MObject& modifier )
{
MStatus status = MS::kFailure;
if( fModifierNodeTypeInitialized ||
fModifierNodeNameInitialized )
{
if( fModifierNodeTypeInitialized )
{
modifier = fDGModifier.createNode(fModifierNodeType,
&status);
}
else if( fModifierNodeNameInitialized )
{
modifier = 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( modifier );
MObject inMeshAttr;
MObject outMeshAttr;
inMeshAttr = depNodeFn.attribute( “inMesh” );
outMeshAttr = depNodeFn.attribute( “outMesh” );
if( inMeshAttr.isNull() || outMeshAttr.isNull() )
{
displayError(“Invalid Modifier: inMesh/outMesh needed”);
status = MS::kFailure;
}
}
return status;
}
createModifierNode() では、モディファイア ノード タイプかモディファイア ノード名の初期化済みデータを使用し、ローカル DG モディファイアを通してモディファイア ノードを作成します。元に戻すとやり直しを比較的単純にするには、DG モディファイアの使用が不可欠です。DG モディファイアはノードを作成していませんが、接続などそのノードのアクションを待ち行列に入れるので、DG モディファイアの doIt() が呼び出されると、すべてが順番に実行されます。これによって、エラーが発生した場合のロールバックの問題が緩和されます。また、createModifierNode() では、inMesh アトリビュートと outMesh アトリビュートがノードに存在するかどうかを確認するためのチェックが行われます。名前だけで判断すると限定的に思えますが、モディファイア ノードのチェーンでは標準的です。polyModifierNode というヘルパー クラス(後のセクションで説明)では、この 2 つの主要アトリビュートが自動的に生成されます。
createModifierNode() の後で initModifierNode() のコールバックが行われ、作成されたクラスでは、これによってノード データが初期化されます。これも、コマンドの制御下で行われます。そこから、最終ステージの connectNodes() に入ります。
connectNodes() は大きなメソッドで、polyShape ノードとモディファイア ノードのすべての変数を処理し、polyShape とモディファイアを接続します。高いレベルから connectNodes() を調べて、制御内容を確認してください。
MStatus polyModifierCmd::connectNodes( MObject& modifierNode )
{
MStatus status;
// Declare our internal processing data structure
//
modifyPolyData data;
// Get the mesh node attrs and plugs
//
status = processMeshNode( data );
MCheckStatus( status, “processMeshNode” );
// Get the upstream node attrs and plugs
//
status = processUpstreamNode( data );
MCheckStatus( status, “processUpstreamNode” );
// Get the modifierNode attributes
//
status = processModifierNode( modifierNode, data );
MCheckStatus( status, “processModifierNode” );
// Process the tweaks on the meshNode
//
status = processTweaks( data );
MCheckStatus( status, “processTweaks” );
// Connect the nodes
//
if( fHasTweaks )
{
status = fDGModifier.connect( data.upstreamNodeShape,
data.upstreamNodeSrcAttr,
data.tweakNode,
data.tweakNodeDestAttr );
MCheckStatus( status, “upstream-tweak connect” );
status = fDGModifier.connect( data.tweakNode,
data.tweakNodeSrcAttr,
modifierNode,
data.modifierNodeDestAttr );
MCheckStatus( status, “tweak-modifier connect” );
}
else
{
status = fDGModifier.connect( data.upstreamNodeShape,
data.upstreamNodeSrcAttr,
modifierNode,
data.modifierNodeDestAttr );
MCheckStatus( status, “upstream-modifier connect” );
}
status = fDGModifier.connect( modifierNode,
data.modifierNodeSrcAttr,
data.meshNodeShape,
data.meshNodeDestAttr );
MCheckStatus( status, “modifier-mesh connect” );
status = fDGModifier.doIt();
return status;
}
connectNodes() は複数のサブセクションに分けられます。最初のサブセクションでは、一般的な処理データの構造が構築されます。この構造には、それぞれの処理メソッド間で必要となる、すべての処理変数が含まれます。この構造は、処理メソッド間で渡される引数の数を減らすために作成されます。そこから処理メソッドのセットが呼び出され、ノードの接続に必要なデータが収集されて、ノード stat が処理されます。その後で、収集されたデータを使用してノードを接続します。
これに続く各処理メソッドの詳細については、完全に文書化された polyModifierCmd.cpp のソース コードを参照してください。
connectNodes() が実行する最初の処理メソッドは processMeshNode() です。この処理メソッドでは、ノード シェイプ、ノード変換、接続データ(inMesh プラグとアトリビュート)など、polyShape ノードで必要なすべてが処理されます。このデータは、渡された modifyPolyData データ構造に格納され、その他の処理メソッドでさらに使用されます。重要なことは、この処理メソッドが実行される順序です。
次の処理メソッド processUpstreamNode() では、コンストラクション ヒストリが存在するケースとコンストラクション ヒストリが存在しないケースで違いがあります。ConnectNodes()は、コンストラクション ヒストリが存在するケース、またはコンストラクション ヒストリは存在しないがコンストラクション ヒストリの記録がオンになっているケースのみで呼び出されます。このケースでは、いずれもコンストラクション ヒストリの追加が許され、より安定して柔軟に処理できるためです。
コンストラクション ヒストリがすでに存在する場合、processUpstreamNode() は processMeshNode() で取得した inMesh プラグを使用し、polyShape ノードのすぐ上のノードを取得します。すぐ上のノードを取得したら、polyShape ノードからそのノードを切断し、モディファイア ノード挿入の準備をします。すべての DG 接続は、メッシュ ノードと上流ノードの間で行われるので、後で接続するために outMesh プラグとアトリビュートのみを取得します。
コンストラクション ヒストリが存在しない場合は、コンストラクション ヒストリ チェーンを作成する必要があります。各コンストラクション ヒストリ チェーンの最初には、コンストラクション ヒストリが作成された時点での polyShape ノードの初期状態を表す非表示の中間(Intermediate)ノードがあります。これは、processUpstreamNode() から polyShape ノードに MFnDAGNode を適用した状態で duplicate() を呼び出すことで作成されます。duplicate() メソッドは、新しい複製シェイプ ノードのために新しいトランスフォーム ノードを作成してから、この新しいシェイプ ノードを複製元のシェイプ ノードと同じトランスフォーム ノードにリペアレント化します。DAG モディファイアで複製によって作成されたトランスフォーム ノードを削除します。DG に対しては、この複製シェイプ ノードは、ヒストリが存在する場合に処理される上流ノードと同じように動作します。つまりすべての接続は、この複製シェイプ ノードと元のシェイプ ノードの間で実行されます。したがって、複製シェイプ ノードに上流ノード情報を設定し、後で接続するために outMesh プラグとアトリビュートを取得します。
processMeshNode() と同じように、3 番目の処理メソッドである processModifierNode() は、connectNodes() でノードを接続するために、モディファイア ノードの inMesh アトリビュートと outMesh アトリビュートを取得します。
コンストラクション ヒストリのため、ツィークは、操作順序を維持するためにモディファイア ノードを追加する前に抽出する必要があります。このため、ツィークが存在する場合は、以下の 2 つのステージが実行されます。
ツィーク ノードの作成(polyTweak)から始まり、ツィークを格納して、pnts アトリビュートでツィークが解析されます。ツィークは、元に戻す操作に備えてクラスの配列メンバーに個別にキャッシュされ、ツィーク ノードに転送するためにローカル MObject 配列にキャッシュされます。ツィークは、抽出したベクトル配列から構成されますが、抽出中に考慮すべきことはほかにもあります。ツィークは pnts アトリビュートに格納されます。DG のアトリビュートとして、接続も含まれることがあります。DG 構造を維持するため、この接続は保存してツィーク ノードに転送する必要があります。
それぞれのベクトル配列の他に、pnts アトリビュートに接続されている、それぞれのノードのプラグ配列、および接続されている pnts 配列のそれぞれのプラグを抽出します。pnts 配列アトリビュートのそれぞれの配列が、複合アトリビュートであることに注意してください。複合アトリビュートのそれぞれのアトリビュートは、特定の頂点ツィークベクトル(x、y、z)の 1 つの軸移動に関連します。
データを抽出したら、polyTweak ノード上でツィークベクトルを適用し、それぞれのツィークベクトル上で再接続します。これでツィーク ノードができ、ヒストリ ストリームに接続する準備が整いました。しかし現状のままで接続すると、結果としてメッシュではツィークの重複が予想されます。これは、メッシュ ノードからツィークが削除されていないためです。このため、ツィーク ノードから 1 回、メッシュ ノードから 1 回、合計 2 回のツィークが適用されます。しかし、MObject 配列を使用してプラグを取り出したことに注意してください。配列アトリビュートに含まれる複合プラグへの MObject 参照を抽出しました。これをツィーク ノードに設定することで、それらはツィーク ノード上に移動されます。processTweaks() には、コメントになっている大きなコード セグメントがあり、そのセグメントはメッシュ ノードからツィークを削除します。このコードを実行しても結果は同じですが、明らかな利点もなくパフォーマンスも低下します。
その他の処理メソッドと同じように、processTweaks() はツィーク ノードの接続データを取得し、connectNodes() がすべてのノードを接続できるようにします。
必要なすべてのノードがセットアップされ、接続データが抽出されました。ノード接続は、MDGModifier::connect calls() でセットアップされます。すべての操作は、最後の MDGModifier::doIt() 呼び出しで実行されます。そこから、DG モディファイアを実行した反対の順序で undoIt() を呼び出して元に戻すをサポートし、前と同じ順序で doIt() を呼び出してやり直しをサポートできます。これは、DG モディファイアが、行ったすべての実行をキャッシュしているために実現できます。同じように polyModifierCmd は、doModifyPoly() を呼び出して、必要なすべてのデータをキャッシュします。このため、undoModifyPoly() と redoModifyPoly() が比較的単純になります。
undoModifyPoly の構造と抽象化レベルは、doModifyPoly() メソッドに非常によく似ています。これは、呼び出されたノード状態を調べて、適切なメソッドに制御を渡します。
MStatus polyModifierCmd::undoModifyPoly()
{
MStatus status;
if( !fHasHistory && !fHasRecordHistory )
{
status = undoDirectModifier();
}
else
{
fDGModifier.undoIt();
// undoCachedMesh() must be called prior to
// undoTweakProcessing() because undoCachedMesh()
// copies the original mesh without tweaks back
// onto the existing mesh. Any changes made before
// the copy will be lost.
//
if( !fHasHistory )
{
status = undoCachedMesh();
fDagModifier.undoIt();
}
status = undoTweakProcessing();
}
return status;
}
undoDirectModifier は、directModifier のように単純ではありません。polyModifierCmd は directModifier が実行する内容を認識しないので、undoDirectModifier がメッシュ全体を元の状態に戻す必要があります。undoDirectModifier は、MObject の動作方法の知識を利用して、MObject ハンドルをそれぞれの MObject ごとに実行します。
MObject は、Maya が所有する内部オブジェクトのハンドルであると言われています。しかしこれは部分的にしか合っていません。MObject はハンドルですが、Maya が常に MObject を所有しているわけではありません。Maya には、参照をカウントするオブジェクトがあります。つまり、オブジェクトの参照ごとにカウントに 1 が加算されます。参照が削除されるたびに、ゼロに達してオブジェクトが削除されるまで、カウントから 1 が差し引かれます。これは、使用されている場合に限って有効であるオブジェクトのようなものです。MObject は、リファレンス数のオブジェクトを参照することがあります。このため、Maya がオブジェクトを所有していても、オブジェクトの MObject ハンドルを維持して、オブジェクトの存続期間を制御できます。その他のタイプのオブジェクトでは、プラグインの呼び出し中でデータが変更される可能性があるので(ノードの削除など)、MObject を維持しないという一般的なヒントが有効、かつ強く推奨されます。
参照がカウントされるタイプのデータは、MFnData クラスと MFnComponent クラスの階層で分類されるオブジェクトです。MFnData オブジェクト タイプには、メッシュの内容全体が組み込まれています。メッシュのバックアップの基礎としてこの概念を使用すると、ダイレクト モディファイアを適切に元に戻すことができます。
cacheMeshData() は、directModifier() の前に、メッシュの使用に関するデータをキャッシュします。メッシュ データのキャッシュには、MObject 概念が使用されます(undoDirectModifier() を参照)。データをバックアップする場合は、現行オブジェクトの参照を取得しないように注意してください。dirty になっている可能性があるデータの参照を維持することになってしまいます。これを避けるには、現行メッシュを複製し、複製メッシュの outMesh アトリビュートから MObject を抽出して MFnData オブジェクトを取得します。取得した MObject は、元のメッシュデータの参照なので、参照カウントに 1 が加算されます。ユーザにこれを意識させないようにするには、複製で作成したノードを削除して仕上げます。ノードを削除しても、参照があるので、メッシュ データは削除されません。
このメソッドは、ツィーク ノードを処理しないことを除けば、processTweaks() メソッドと同じです。pnts アトリビュートを分析し、ツィークキャッシュ データ メンバーにツィークベクトルを抽出します。
メッシュのバックアップに必要なデータをキャッシュして undoDirectModifier() で使用できます。ツィークがある場合とない場合では、polyShape ノードのデータ フローが異なります。これは、バックアップ メッシュを再適用する方法に直接的な影響を与えます。
ツィークは、ノード内のデータ フローのため、バックアップ メッシュの再適用に影響します。ツィークがないノードでは、outMesh の値をバックアップ メッシュに設定できます。
ツィークがあるノードでは、バックアップ メッシュを cachedInMesh にコピーしてノードの同期を維持する必要があります。この場合は、polyShape ノードを複製して複製シェイプの outMesh をバックアップ メッシュに設定し、複製した polyShape ノードを元のノードに接続します。(DG)評価を強制実行した後で、複製した polyShape ノードを切断して削除します。これで、outMesh にはバックアップ メッシュが維持されます。次に undoTweaksProcessing で初期ツィークをノードに再適用し、ノードで cachedInMesh にバックアップ メッシュをコピーして、内部ノードの同期化を実行します。
connectNodes() を元に戻す必要がある場合、MDGModifier::undoIt() メソッドは、connectNodes() によって作成されたすべての接続とノードを元に戻します。最初にヒストリがなかった場合、processUpstreamNode() も DAG モディファイアを使用したことに注意してください。この場合は、MDagModifier::undoIt() を呼び出す前に、別の操作を実行する必要があります。
これは、ヒストリを作成して操作を元に戻すため、ヒストリを削除する必要があるからです。削除は直接的ですが、ノードの outMesh アトリビュートには、最終のノード評価が維持されている、つまりモディファイアがまだ含まれています。このため、ノードで「キャッシュされている」メッシュを元に戻す必要があります。これを実行するには、メソッド undoCachedMesh() を呼び出します。メッシュ データの復元に続けてツィークを復元し、元のメッシュを残します。
undoDirectModifier() の場合と同じように、undoCachedMesh() に含まれる操作は、ノードのデータ フローが変化するため、ツィークの存在によって決まります。ヒストリがないノードに戻し、ノードと直接インタフェースを取ってメッシュを復元する必要があるからです。
ツィークが存在しない場合は、outMesh がノードを表すジオメトリになるため、outMesh を復元するだけで済みます。これを実行するには、ヒストリ チェーンを始めるために作成された複製シェイプ ノードを使用します。最初にヒストリが存在していなかった場合に限ってこれを実行することに注意してください。複製シェイプ ノードの outMesh を取得し、シェイプ ノードの outMesh にそのメッシュ データをコピーして、ノードを初期状態に戻します。
ツィークが存在する場合は、undoDirectModifier がツィークを処理する場合と同じ方法でノードにアクセスする必要がありますが、ここではシェイプ ノードを複製する必要がありません。すでに複製が存在するからです。複製シェイプの outMesh を元のシェイプの inMesh にローカルDG モディファイアで再接続し、DG 評価を強制実行します。次に、同じDG モディファイアで接続を元に戻します。元のメッシュ データが outMesh に入ってからツィークが再適用されて、outMesh は cachedInMesh にコピーされます。
redoModifyPoly() は簡単です。操作に必要なものはすべて doModifyPoly() で初期化して設定してあるからです。redoModifyPoly() にも doModifyPoly() と同様の構造が保持されます。directModifier() の場合、doModifyPoly() は、メッシュ データを再びキャッシュすることなくメソッドをリコールします。これは、クラスにすでに存在するからです。connectNodes() の場合は、MDGModifier::doIt() をリコールし、doModifyPoly() が以前セットアップした操作を再実行します。
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.
//
status = directModifier( meshNode );
}
else
{
// Call the redo on the DG and DAG modifiers
//
if( !fHasHistory )
{
status = fDagModifier.doIt();
}
status = fDGModifier.doIt();
}
return status;
}
polyModifierCmd の機能について一般的に解釈すると、それに基づいてコマンドを実装する方法という問題に直面します。規則はかなり簡単です。polyModifierCmd は、Maya がノードを扱う場合と同じような方法でプロセスを構造化します。ここでは、ファクトリという概念が導入されます。以下のセクションでは、polyModifierCmd に基づいてコマンドを実装するという状況で polyModifierFty と polyModifierNode について説明します。この動作方法の詳細については、それぞれのソース ファイルを参照してください。
polyModifierCmd の内部動作からは、directModifier()、およびモディファイア ノードの内部の処理部という、冗長コードが潜在する 2 つの場所を確認できます。この 2 つは、入力の取得方法が異なることを除くと、結果的に同じことを実行します。コードの重複を減らすため、ファクトリという概念が導入されました。ファクトリは、メッシュの修正を実装するクラス構造としてだけ存在します。これには、モディファイアを呼び出すことのできる基本インタフェースがあります。
ファクトリのガイドとして、polyModiferFty というベース ファクトリ クラスが提供されています。このベース ファクトリ クラスからファクトリを派生させることができます。これには特に機能はありませんが、修正する場合のアウトラインとなります。
class polyModifierFty
{
public:
polyModifierFty();
virtual ~polyModifierFty();
// Pure virtual doIt()
//
virtual MStatus doIt() = 0;
};
polyModifierFty と同じように、polyModifierCmd に関連して使用する、すべてのモディファイア ノードのフレームワークとなる、別のガイダンス クラスが提供されています。このクラスは、モディファイア ノードが動作するには、inMesh アトリビュートと outMesh アトリビュートが必要であることなどを提案します。
class polyModifierNode
{
public:
polyModifierNode();
virtual ~polyModifierNode();
// There needs to be an MObject handle declared for
// each attribute that the node will have. These handles
// are needed for getting and setting the attribute
// values later.
//
static MObject inMesh;
static MObject outMesh;
};
polyModifierCmd コマンドの実装についての詳細は、「splitUVCmd の例」(次のセクション)とソース コードの両方を参照してください。ソース コード内では、それぞれのクラスの一般的なガイドラインが提供されています。