splitUVCmd の例
 
 
 

(ソース コードは、開発キットの plugins ディレクトリにあります)

splitUV コマンドは、ポリゴン メッシュで選択した UV の共有を解除する、つまり「分割する」MEL コマンドです。UV は 2D テクスチャ プレーン上のポイントで、テクスチャをメッシュにマッピングするために使用します。UV をメッシュに適切に関連付けるには、それぞれのフェースのマッピングまたはマッピング解除を行います。フェースをマッピングすると、そのフェースに属するそれぞれの頂点が UV に関連付けられます。このリレーションシップをフェース - 頂点リレーションシップと呼びます。詳細については、フェース頂点を参照してください。

UV の分割の意味についてさらに理解を深めるため、Maya で以下の手順を実行してください。

  1. 3x3 四方のポリゴン プレーンを作成します。
  2. プレーンを選択した状態で、UV テクスチャ エディタ(UV Texture Editor)UV の編集 > UV テクスチャ エディタ(Edit UVs > UV Texture Editor))を開きます。
  3. 選択モードを UV に変更します。
  4. プレーン内部の UV(境界に沿ったものではない)を選択します。
  5. 移動ツール(Move Tool)を選択します。
  6. UV をドラッグし、UV が 1 つしかないことを確認します。
  7. 選択モードをエッジ(Edges)に変更します。
  8. 移動した UV を囲む、4 つすべてのエッジを選択します。
  9. UV の編集 > UV エッジのカット(Edit UVs > Cut UV Edges)を選択します。
  10. 選択モードを UV に戻します。
  11. 選択した UV を 1 回クリックします。UV の上でドラッグしないように注意してください。ドラッグすると、すべてが選択されてしまいます。
  12. 共有されていない UV の表示をオンにします(ディスプレイ > ポリゴン > 共有されていない UV(Display > Polygons > Unshared UVs))。
  13. UV をドラッグし、その UV がフェース間で共有されておらず、独自のフェースを含む単一の UV であることを確認します。
  14. (オプション)テクスチャ境界の表示をオンにして(ディスプレイ > ポリゴン > テクスチャの境界エッジ(Display > Polygons > Texture Border Edges))カット操作によって新しくできた境界を表示します。

UV エッジのカット(Cut UV Edges) コマンドはメッシュのエッジ上で動作し、splitUV は UV 上で動作します。

UV は、フェース頂点によってインデックスが付いた、単一の 1 次配列に保存されます。それぞれのフェース頂点は特定 UVId をインデックス付けし、それぞれの UVId はテクスチャ マッピングの 1 つの UV ポイントを表します。このため、UV を共有するフェース数と同じ数だけ、共有 UVId をインデックス付けします。詳細については、UV を参照してください。

UV を共有するフェースの総数から 1 を引いた数の UV を追加してください(同じ 2D 座標で)。1 を引くのは、すでに共有フェースに関連付けられた最低 1 つの UV があることがわかっているため、作成と関連付けを行うフェースと UV を 1 つ減らします。ここで、元の UV を共有するフェースに、それぞれ新しい UV を関連付け、1 つのフェースを元の UV にインデックス付けしたまま残します。これにより、それぞれのフェースが、それぞれのフェース頂点の場所に共有されていない UV を持つようになります。

初期実装

最初に入力値、この場合は選択されている UV を収集します。セレクション リストを取得し、フィルタ処理して、UV が選択されている最初のオブジェクトを検索します。わかりやすくするために、UV が選択されている最初のオブジェクトのみを考慮します。これは、簡単に拡張して、複数のオブジェクトで動作させることができます。

// Get the active selection list and filter for poly objects.
// Also create a selection list iterator to parse the list
//
MSelectionList selList;
MGlobal::getActiveSelectionList( selList );
MItSelectionList selListIter( selList );
selListIter.setFilter( MFn::kMesh );
// Declare a dagPath and component to store a reference to the
// mesh object and selected components
//
MDagPath dagPath;
MObject component;
// Now parse the selection list for poly objects with selected
// UVs
//
for( ; !selListIter.isDone(); selListIter.next() )
{
    selListIter.getDagPath( dagPath, component );
    // Check for selected UVs
    //
    if( component.apiType() == MFn::kMeshMapComponent )
    {
        break;
    }
}
// Now we break down the component object into an int array
// of UVIds
//
MFnSingleIndexedComponent compFn( component );
MIntArray selUVs;
compFn.getElements( selUVs );

これで、操作するオブジェクトと選択されている UV を取得しました。操作を実行する前に、一部の前処理データを収集する必要があります。前処理データとは、選択されているそれぞれの UV を共有するフェースと UV 頂点関連です。最後に、複数の UV セットが multiUV セットに正しく対応するようにカバーする必要があります。同時に選択できるのは 1 つの UV セットの UV だけなので、現在アクティブになっている UV セットにアクセスしてそのセットで操作を実行するだけで済みます。

// Declare our processing variables
//
MObject mesh;
MString selUVSet;
MIntArray selUVFaceIdMap;
MIntArray selUVFaceOffsetMap;
MIntArray selUVLocalVertIdMap;
// Make sure our dagPath points to a shape node. That is where
// the topological/geometry data is stored (not on the transform)
//
dagPath.extendToShape();
mesh = dagPath.node();
// Initialize a mesh function set to our mesh
//
MFnMesh meshFn( mesh );
// Get the current UV set name
//
meshFn.getCurrentUVSetName( selUVSet);
// Now parse the mesh for face and vertex UV associations
//
MItMeshPolygon polyIter( mesh );
int i;
int j;
int offset = 0;
int selUVsCount = selUVs.length();
for( i = 0; i < selUVsCount; i++ )
{
    // Append the current offset in the faceId map to the
    // faceOffset map so we have an index reference into the
    // faceId map for each selected UV. In other words,
    // for each offset value, we have a number of faces equal
    // to (faceOffsetMap[i+1] – faceOffsetMap[i]) that share
    // the UV that that offset value represents.
    //
    selUVFaceOffsetMap.append( offset );
    // Parse the mesh for faces which share the current UV
    //
    for( ; !polyIter.isDone(); polyIter.next() )
    {
        // Only continue if the face is mapped
        //
        if( polyIter.hasUVs() )
        {
            // Now parse the vertices of each face and check for
            // the current UV
            //
            int polyVertCount = polyIter.polygonVertexCount();
            for( j = 0; j < polyVertCount; j++ )
            {
                int UVIndex = 0;
                polyIter.getUVIndex( j, UVIndex );
                // If we find the UV on this face, append the faceId,
                // append the local vertex (relative to the current
                // face) and increment our offset.
                //
                if( UVIndex == selUVs[i] )
                {
                    selUVFaceIdMap.append( polyIter.index() );
                    selUVLocalVertIdMap.append(j);
                    offset++;
                    break;
                }
            }
        }
    }
}
// Finally append the last offset value so we can obtain the
// number of faces that share the last UV in the list.
//
selUVFaceOffsetMap.append( offset );

フェースとフェース頂点の関連性は、Maya がポリゴンのトポロジ データを保存する場合と同じ技法で保存されます。この技法は、選択されているそれぞれの UV によって共有されているフェースのリストが累積する部分のコードでよくわかります。多次元配列を作成して、選択されているそれぞれの UV で共有されているフェースのリストを保存するのではなく、1 次元配列、つまり 1 つのデータ配列にすべてを保存します。これを実行するには、別の 1 次元配列(インデックス配列)を作成し、選択されているそれぞれの UV がフェースのリストを始めるそのデータ配列のインデックス値を保存します。

それぞれの UV では、特定の UV を共有しているフェースを探すためにメッシュを分析します。これは、メッシュの各フェースのフェース頂点を分析し、関連 UVId を調べて UVId とカレントの UV を比較することで実行します。ここで、フェース ID とローカル頂点 ID(カレント フェースに相対で、0 から(faceVertexCount – 1)で列挙)を保存します。UV はフェース頂点ベースで割り当てられているので、グローバル頂点 ID やメッシュ頂点 ID ではなく、ローカル頂点 ID を考慮します。

// Declare UV count variables so we can keep create and
// keep track of the indices of the new UVs
//
int currentUVCount = meshFn.numUVs( selUVSet );
// For each UV in our list of selected UVs, split the UV.
//
for( i = 0; i < selUVsCount; i++ )
{
    // Get the current faceId map offset
    //
    offset = selUVFaceOffsetMap[i];
    // Get the U and V values of the current UV
    //
    float u;
    float v;
    int UVId = selUVs[i];
    meshFn.getUV( uvId, u, v, &selUVSet );
    // Get the number of faces sharing the current UV
    //
    int faceCount = selUVFaceOffsetMap[i+1]–selUVFaceOffsetMap[i];
    // Arbitrarily choose that the last faceId in the list
    // of faces sharing this UV will keep the original UV
    //
    for( j = 0; j < faceCount – 1; j++ )
    {
        // Create a new UV (setUV dynamically sizes the UV array
        // if the index value passed in exceeds the length of the
        // UV array) with the same 2D coordinates as our UV.
        //
        meshFn.setUV( currentUVCount, u, v, &selUVSet );
        // Get the face and face-vertex info so we can assign
        // our newly created UV to one of the faces in the list
        // of faces sharing this UV
        //
        int localVertId = selUVLocalVertIdMap[offset];
        int faceId = selUVFaceIdMap[offset];
        // Associate the UV with the particular face vertex
        //
        meshFn.assignUV( faceId,
            localVertId,
            currentUVCount,
            &selUVSet );
        // Increment our counters so we can create another new UV
        // at the currentUVCount index. Increment the offset, so we
        // can associate the next new UV with the next face in the
        // the list of faces sharing this UV
        //
        currentUVCount++;
        offset++;
    }
}

実際に分割を行うためにコールするメソッドは、主に以下の 2 つです。

最初のメソッド setUV() をコールし、新しい UVId を作成します。このメソッドでは UV 配列が自動的に拡大され、メソッドに渡されたインデックスが正しく処理されます。コードには currentUVCount という名前の変数が表示されます。currentUVCount は、新しい UV が作成されるたびに増分されます。currentUVCount は常に、UV 配列中一番最後のエレメントより 1 大きなエレメントのインデックスを維持しています。ループで反復するごとに 1 を加算すると、新しい UV を 1 つずつ作成できます。

次のメソッド assignUV() は、指定された UVId をフェースとフェース頂点に関連付けるためにコールします。

Maya アーキテクチャへの統合

コンストラクション ヒストリとツィークを含むポリゴン メッシュの修正は、かなり複雑です。メッシュにヒストリがない場合は、UV の共有解除をメッシュ自体で直接実行できます。メッシュにヒストリがある場合は、メッシュ ノードのメッシュはコンストラクション ヒストリの上流ノードの DG 評価によって上書きされ、メッシュに直接加えられた修正は失われます。このような場合でも、ツィークの存在は、メッシュ上で修正を書き込む適切な場所を変更します。

メッシュ ノードを調べて状態を分析し、状態に従って操作を適用する必要があります。MPxCommand クラスの polyModifierCmd(polyModifierCmd の例を参照)は splitUV コマンド作成時に同時に開発され、コンストラクション ヒストリとツィークの処理を取り出すために役立ちます。polyModifierCmd は、ポリゴン メッシュを修正するコマンド用に設計された一般的なコマンド クラスです。これは、コンストラクション ヒストリとツィークの両方を考慮しながら、poly コマンドが直接メッシュを修正するコードを取得することができ、Maya フレームワークに継ぎ目なく統合する方法を提供します。

polyModifierCmd は API の良い使用例で、コンストラクション ヒストリとツィークがどのように動作するかを示します。

polyModifierCmd で機能強化された splitUV

このセクションを進める前に、polyModifierCmd の例を読んでおいてください。このセクションでは、polyModifierCmd に基づいてコマンドを実装する方法を手順ごとに説明します。

polyModifierCmd には、以下のように、処理する必要がある 3 つの主な部分があります。

splitUV ファクトリ

ファクトリは splitUVCmd の最下位レベルで、入力(のセット)に対する操作を実行します。入力は UV ID の整数配列と修正するメッシュへの参照です。残りはファクトリ内部に適合します。以下は、splitUVFty ファクトリ クラス インタフェースです。

class splitUVFty : polyModifierFty
{
    public:
        splitUVFty();
        virtual ~splitUVFty();
        void setMesh( MObject mesh );
        void setUVIds( MIntArray uvIds );
        // polyModifierFty inherited methods
        //
        MStatus doIt();
    private:
        // Mesh Node
        //
        MObject fMesh;
        // Selected UVs
        //
        MIntArray fSelUVs;
};

splitUV ノード

ファクトリを展開する方法は 2 つあります。1 つは splitUV ノードによって行う方法で、もう 1 つは、ノードが妥当でない場合の例外用コマンドによる直接的な方法です。splitUV ノードは、DG 内で既存のヒストリ チェーンへの追加または構築を行う場合に使用します。

DG 評価がダーティなノードを通じて伝搬されると、DG は、オリジナル メッシュのコピー(オリジナルは、ノードの状態を参照するもので、このヒストリの先頭を表す)が配置されているヒストリ チェーンの最上位から評価します。次にそのメッシュのコピーが取られ、それぞれのノードを順番に渡されて、それぞれのノード評価でメッシュが変更されます。最終シェイプに達すると、すべてのモディファイアがヒストリ チェーンに保存されているシェイプにメッシュが配置されます。splitUV ノードは、メッシュ入力、修正する UV の入力を取り、ファクトリのインスタンスにそのデータを渡します。結果としてのメッシュは、メッシュ出力アトリビュートを通して渡されます。

class splitUVNode : polyModifierNode
{
    public:
        splitUVNode();
        virtual ~splitUVNode();
        virtual MStatus compute(const MPlug& plug, MDataBlock& data);
        static void* creator();
        static MStatus initialize();
    private:
        // Note: There needs to be an MObject handle for each
        // attribute on the node. These handles are needed for
        // setting and getting values later. The standard inMesh
        // and outMesh attributes are already inherited from
        // polyModifierNode, thus we only need to declare splitUVNode
        // specific attributes
        //
        static MObject uvList;
        // Node type identifier
        //
        static MTypeId id;
        // We instantiate a copy of our splitUV factory on the node
        // for it to perform the splitUV operation
        //
        splitUVFty fSplitUVFactory
};

標準ノード インタフェースは、上のクラス宣言にあります。注意すべき違いは、プライベート メンバーに関するものです。クラス階層により、splitUVNode は、polyModifierNode から inMesh アトリビュートと outMesh アトリビュートを継承します。splitUVNode でのみ使用する入力アトリビュート(操作する UV リスト)を追加します。

ノード クラスには、splitUV ファクトリのインスタンスがあります。ノードごとに別個のファクトリを作成するため、splitUVFty の実装には操作をコールしているノードに関する予備知識は不要です。基本ノード セットアップを続け、上のインタフェースの基本メソッドを実装し、アトリビュートの作成と関連付けを行い、タイプ ID を割り当てます。:

MTypeId splitUVNode::id( 0x34567 );
MStatus splitUVNode::creator()
{
    return new splitUVNode();
}
MStatus splitUVNode::initialize()
{
    MStatus status;
    MFnTypedAttribute attrFn;
    uvList = attrFn.create("inputComponents",
        "ics",
        MFnComponentListData::kComponentList);
    // To be stored during file-save
    attrFn.setStorable(true);
    inMesh = attrFn.create("inMesh",
        "im",
        MFnMeshData::kMesh);
    // To be stored during file-save
    attrFn.setStorable(true);
    // outMesh is read-only because it is an output attribute
    //
    outMesh = attrFn.create("outMesh",
        "om",
        MFnMeshData::kMesh);
    attrFn.setStorable(false);
    attrFn.setWritable(false);
    // Add the attributes we have created for the node
    //
    status = addAttribute( uvList );
    if( !status )
    {
        status.perror("addAttribute");
        return status;
    }
    status = addAttribute( inMesh );
    if( !status )
    {
        status.perror("addAttribute");
        return status;
    }
    status = addAttribute( outMesh );
    if( !status )
    {
        status.perror("addAttribute");
        return status;
    }
    // Set up a dependency between the inputs and the output.
    // This will cause the output to be marked dirty when the
    // input changes. The output will then be recomputed the
    // next time it is requested.
    //
    status = attributeAffects( inMesh, outMesh );
    if( !status )
    {
        status.perror("attributeAffects");
        return status;
    }
    status = attributeAffects( uvList, outMesh );
    if( !status )
    {
        status.perror("attributeAffects");
        return status;
    }
    return MS::kSuccess;
}

最後に compute() メソッドの実装に移ります。compute メソッドは、それほど複雑ではありません。ファクトリで操作を実行するので、compute メソッドでは、修正する適切なメッシュへの参照をファクトリに提供するだけで済みます。

すべてのプラグイン ノードと同じように起動し、MPxNode から継承したノードの「state」アトリビュートを処理します。「state」アトリビュートには、DG によるノードの扱い方を表す、短い整数が保存されています。ある意味で、これは、DG 評価中にノードが扱われる方法を変更するオーバーライド メカニズムです。プラグイン ノードの場合、重要な状態は「HasNoEffect」状態か「PassThrough」状態のみで、この状態ではノードは完全に無視されます。透過的に動作するノードの場合は、渡すメッシュを変更せずに、inMesh を outMesh に転送する必要があります。その他の場合、ノードは通常どおりに動作します。

ノード状態をチェックした後、コンポーネント リストと入力メッシュから UV を取得し、入力メッシュを出力メッシュに割り当て、この参照をファクトリに渡します。入力メッシュを出力メッシュに割り当てると、出力メッシュ上で直接操作が可能になり、出力メッシュには修正済みメッシュが保存されます。そこから、ファクトリが残りの操作を実行します。

MStatus splitUVNode::compute(const MPlug& plug, MDataBlock& data)
{
    MStatus status = MS::kSuccess;
    // Retrieve our state attribute value
    //
    MDataHandle stateData = data.outputValue(state,&status);
    MCheckStatus( status, "ERROR getting state" );
    // Check for the HasNoEffect/PassThrough state
    //
    // (stateData is stored as a short)
    //
    // (0 = Normal)
    // (1 = HasNoEffect/PassThrough)
    // (2 = Blocking)
    // ..
    //
    if( stateData.asShort() == 1 )
    {
        MDataHandle inputData = data.inputValue(inMesh,&status);
        MCheckStatus(status, "ERROR getting inMesh");
        MDataHandle outputData = data.outputValue(outMesh,&status);
        MCheckStatus(status, "ERROR getting outMesh");
        // Simply redirect the inMesh to the outMesh
        //
        outputData.set(inputData.asMesh());
    }
    else
    {
        // Check which output value we have been asked to compute.
        // If the node doesn’t know how to compute it, return
        // MS::kUnknownParameter.
        //
        if( plug == outMesh )
        {
            MDataHandle inputData = data.inputValue( inMesh,
                &status );
            MCheckStatus(status, "ERROR getting inMesh");
            MDataHandle outputData = data.outputValue( outMesh,
                &status );
            MCheckStatus(status, "ERROR getting outMesh");
            // Now, we retrieve the input UV list
            //
            MDataHandle inputUVs = data.inputValue( uvList,
                &status );
            MCheckStatus(status, "ERROR getting uvList");
            // Copy the inMesh to the outMesh so we can operate
            // directly on the outMesh
            //
            outputData.set(inputData.asMesh());
            MObject mesh = outputData.asMesh();
            // Retrieve the UV list from the component list
            //
            // Note, we use a component list to store the components
            // because it is more compact memory wise. (ie.
            // comp[81:85] is smaller than comp[81],...,comp[85])
            //
            MObject compList = inputUVs.data();
            MFnComponentListData compListFn( compList );
            unsigned i;
            int j;
            MIntArray uvIds;
            for( i = 0; i < compListFn.length(); i++ )
            {
                MObject comp = compListFn[i];
                if( comp.apiType() == MFn::kMeshMapComponent )
                {
                    MFnSingleIndexedComponent uvComp(comp);
                    for( j = 0; j < uvComp.elementCount(); j++ )
                    {
                        int uvId = uvComp.element(j);
                        uvIds.append(uvId);
                    }
                }
            }
            // Set the mesh object and uvList on the factory
            //
            fSplitUVFactory.setMesh( mesh );
            fSplitUVFactory.setUVIds( uvIds );
            // Now, call the factory to perform the splitUV
            //
            status = fSplitUVFactory.doIt();
            // Mark the outputMesh as clean
            //
            outputData.setClean();
        }
        else
        {
            status = MS::kUnknownParameter;
        }
    }
    return status;
}

splitUV コマンド

これで splitUVNode を取得したので、最後に残っているのは splitUV コマンドです。これは、これまでのすべてを結び付けるものです。メッシュの修正方法が決まるのはここです(polyModifierCmd を通して)。このコマンドは操作を管理し、ユーザとの最上位レベルのインタフェースとなります。

polyModifierCmd の子クラスである splitUVCmd には、特定の polyModifierCmd メソッドにオーバーライドして入力を取得する以外に、実行することはあまりありません。

class splitUV : polyModifierCmd
{
    public:
        splitUV();
        virtual ~splitUV();
        static void* creator();
        bool isUndoable();
        // MPxCommand inherited methods
        //
        MStatus doIt( const MArgList& );
        MStatus redoIt();
        MStatus undoIt();
        // polyModifierCmd inherited methods
        //
        MStatus initModifierNode( MObject modifierNode );
        MStatus directModifier( MObject mesh );
    private:
        // Private methods
        //
        bool validateUVs();
        MStatus pruneUVs();
        // Private members
        //

        // Selected UVs
        //
        // We store two copies of the UVs, one that is passed down to
        // the node and another kept locally for the directModifier.
        // Note, the MObject member, fComponentList, is only ever
        // accessed during a single call of a plugin, never between
        // calls where its validity is not guaranteed.
        //
        MObject fComponentList;
        MIntArray fSelUVs;
        // splitUV Factory
        //
        // This factory is for the directModifier to have access to
        // operate directly on the mesh.
        //
        splitUVFty fSplitUVFactory;
};

これは、標準 MPxCommand クラス インタフェースとかなり似ています。ただし、polyModifierCmd の継承とパフォーマンス向上メソッドのため、多少の違いがあります。以下の 2 つのメソッドを無効にする必要があります。

  • initModifierNode()
  • directModifier()

initModifierNode() は、モディファイア ノードの inMesh 以外の入力、このケースでは splitUVNode を初期化します。これは入力の初期化に限定されません。必要に応じて、カスタム ノードの初期化もできます。ヒストリの作成が許可されている場合は、このメソッドがコールされてから、モディファイア ノードがヒストリ チェーンに配置されます。たとえばこのケースでは、splitUVNode の UVList の入力を初期化します。

MStatus splitUV::initModifierNode( MObject modifierNode )
{
    MStatus status;
    // Tell the splitUVNode which UVs to operate on.
    // 
    MFnDependencyNode depNodeFn( modifierNode );
    MObject uvListAttr;
    uvListAttr = depNodeFn.attribute( "inputComponents" );
    // Pass the component list down to the node.
    // To do so, we create/retrieve the current plug
    // from the uvList attribute on the node and simply
    // set the value to our component list.
    //
    MPlug uvListPlug( modifierNode, uvListAttr );
    status = uvListPlug.setValue( fComponentList );
    return status;
}

directModifier() は、メッシュ ノードにヒストリがなくヒストリを記録するプリファレンスがオフになっている例外のみでコールされるメソッドです。この状態から、ヒストリ チェーンをまったく含めたくないことがわかります。このため、polyModifierCmd では DG の使用が禁止されます。その結果、メッシュをダイレクト モディファイアすることになります。polyModifierCmd の説明では、この状態の意味と代替アプローチについて詳しく説明しています。しかし、認識する必要があるのは、メッシュを直接的に操作する方法を提供しなければならないということだけで、これは概念のセクションで完了しています。ファクトリのインスタンスと修正する UV のコピーが(splitUVNode のコンポーネント リストに対して)、整数配列フォーマットでコマンドに維持されているのはこのためです。

選択した UV の 2 つのコピーを別々のフォーマットで保存するのはなぜでしょうか。プラグイン コール間で MObject の有効性が保証されていないからです(redoIt() コールも含む)。directModifier()はredoIt() でコールされるため、コール間では MObject コンポーネント リストの有効性に依存します。コマンドで 2 つのコピーが保存されるのはこのためです。整数配列をコンポーネント リストではなくノード入力として受け取るようにノードを修正して、操作を合理化することもできますが、これは、パフォーマンスと保存のバランスの問題です。

この入力を使用すると、directModifier() メソッドは以下のように単純になります。

MStatus splitUV::directModifier( MObject mesh )
{
     MStatus status;
     fSplitUVFactory.setMesh( mesh );
     fSplitUVFactory.setUVIds( fSelUVs );
     // Now, call the factory to perform the splitUV
     //
     status = fSplitUVFactory.doIt();
     return status;
}

パフォーマンス向上メソッドについて説明する前に、MPxCommand 継承メソッドについて説明します。以下のメソッドでは、コマンドのパフォーマンスがわずかに調整される方法をより良く評価できます。

  • doIt()
  • undoIt()
  • redoIt()

doIt() メソッドは、操作全体を管理しながら、入力を取得して残りの操作をさまざまな部分に実行する主要メソッドです。名前からわかるように、doIt() メソッドは、コマンドを初期化して操作を実行するメソッドです。このため、splitUV の doIt() メソッドはこれを正確に実行します。

最初に、元の実装と同じように、UV が選択されているオブジェクトのセレクション リストを分析します。次に polyModifierCmd 設定を初期化し、パフォーマンス向上メソッドをコールして doModifyPoly() メソッドを発行します。これは、polyModifierCmd バージョンの doIt() とみなすことができます。また、コマンドの不適切な使用についてユーザに通知するために、コード内には適切なエラーが通知されるようになっています。

MStatus splitUV::doIt( const MArgList& )
{
    MStatus status;
    MSelectionList selList;
    MGlobal::getActiveSelectionList( selList );
    MItSelectionList selListIter( selList );
    selListIter.setFilter( MFn::kMesh );
    // Initialize our component list
    //
    MFnComponentListData compListFn;
    compListFn.create();
    // Parse the selection list
    //
    bool found = false;
    bool foundMultiple = false;
    for( ; !selListIter.isDone(); selListIter.next() )
    {
        MDagPath dagPath;
        MObject component;
        selListIter.getDagPath( dagPath, component );
        if( component.apiType() == MFn::kMeshMapComponent )
        {
            if( !found )
            {
                // Add the components to our component list.
                // ‘component’ holds all selected components
                // on the given object, so only a single call
                // to add is needed.
                //
                compListFn.add( component );
                fComponentList = compListFn.object();
                // Locally store the selected UVIds in the command
                // int array member, fSelUVs
                //
                MFnSingleIndexedComponent compFn( component );
                compFn.getElements( fSelUVs );
                // Ensure that our DAG path is pointing to a
                // shape node. Set the DAG path for polyModifierCmd.
                //
                dagPath.extendToShape();
                setMeshNode( dagPath );
                found = true;
            }
            else
            {
                // Since we are only looking for whether or not there
                // are multiple objects with selected UVs, break out
                // once we have found one other object.
                //
                foundMultiple = true;
                break;
            }
        }
    }
    if( foundMultiple )
    {
        displayWarning( "Only operates on first found mesh" );
    }
    // Set the modifier node type for polyModifierCmd
    //
    setModifierNodeType( splitUVNode::id );
    if( found )
    {
        if( validateUVs() )
        {
            // Now, pass control over to polyModifierCmd
            //
            status = doModifyPoly();
            if( status == MS::kSuccess )
            {
                setResult( "splitUV command succeeded!" );
            }
            else
            {
                setResult( "splitUV command failed!" );
            }
        }
        else
        {
            displayError( "Selected UVs are not splittable" );
            status = MS::kFailure;
        }
    }
    else
    {
        displayError( "Unable to find selected UVs" );
        status = MS::kFailure;
    }
    return status;
}

元に戻す/やり直しメカニズムは、MPxCommand から継承した undoIt() メソッドと redoIt() メソッドによってサポートされます。このメソッドは、実行する最初の doIt() からキャッシュされたデータを使用します。splitUV と polyModifierCmd も、undoModifyPoly() と redoModifyPoly() という形式で独自の元に戻す/やり直しをサポートしてこれを実行します。splitUV は polyModifierCmd に依存して操作を実行するので、元に戻す/やり直しによって polyModifierCmd に元に戻す/やり直しが転送されます。このため、splitUV の undoIt() メソッドと redoIt() メソッドは非常に直接的です。

MStatus splitUV::redoIt()
{
    MStatus status;
    status = redoModifyPoly();
    if( status == MS::kSuccess )
    {
        setResult( "splitUV command succeeded!" );
    }
    else
    {
        setResult( "splitUV command failed!" );
    }
    return status;
}
MStatus splitUV::undoIt()
{
    MStatus status;
    status = undoModifyPoly();
    if( status == MS::kSuccess )
    {
        setResult( "splitUV undo succeeded!" );
    }
    else
    {
        setResult( "splitUV undo failed!" );
    }
    return status;
}

doIt() での validateUVs() のコールはパフォーマンス向上メソッドです。このメソッドは、選択されている UV 上で主に事前状態チェックを実行しますが、分割できない UV の選択済み UV リストを切り取り、コマンドのパフォーマンスを最適化します。これにより、操作の不必要なループが削減されます。分割できない UV をチェックするため、メッシュのパスが余計に必要になりますが、これはコマンドを初めてコールした場合のみです。それ以降の redoIt() コールやノード評価は速くなります。

UV が無効で分割できない場合を定義するには、操作の定義を参照してください。splitUV は、ある UV を共有するフェースのそれぞれに、固有で非共有の UV を提供します。このため、複数のフェースで共有されている場合に限って、UV は分割されます。validateUVs メソッドはメッシュを分析し、それぞれの UV を共有しているフェースの情報を取得して有効な UV をマークします。有効な UV のリストは pruneUVs() メソッドに送信され、コンポーネント リストとローカルに保存されている選択済み UV の整数配列を置き換えます。

bool splitUV::validateUVs()
{
    // Get the mesh that we are operating on
    //
    MDagPath dagPath = getMeshNode();
    MObject mesh = dagPath.node();
    // Get the number of faces sharing each UV
    //
    MFnMesh meshFn( mesh );
    MItMeshPolygon polyIter( mesh );
    MIntArray selUVFaceCountArray;
    int i;
    int j;
    int count = 0;
    selUVsCount = fSelUVs.length();
    for( i = 0; i < selUVsCount; i++ )
    {
        for( ; !polyIter.isDone(); polyIter.next() )
        {
            if( polyIter.hasUVs() )
            {
                int UVIndex = 0;
                polyIter.getUVIndex( j, UVIndex );
                // If we have a matching UVId, then we have a
                // face which shares this UV, so increment the
                // count.
                //
                if( UVIndex == fSelUVs[i] )
                {
                    count++;
                    break;
                }
            }
        }
        selUVFaceCountArray.append( count );
    }
    // Now, check to make sure that at least one UV has more than
    // one face sharing it. So long as we have at least one valid
    // UV, we should proceed with the operation by returning true
    //
    bool isValid = false;
    MIntArray validUVIndices;
    for( i = 0; i < selUVsCount; i++ )
    {
        if( selUVFaceCountArray > 1 )
        {
            isValid = true;
            validUVIndices.append(i);
        }
    }
    if( isValid )
    {
        pruneUVs( validUVIndices );
    }
    return isValid;
}
MStatus splitUV::pruneUVs( MIntArray& validUVIndices )
{
    MStatus status = MS::kSuccess;
    unsigned i;
    MIntArray validUVIds;
    for( i = 0; i < validUVIndices.length(); i++ )
    {
        int uvIndex = validUVIndices[i];
        validUVIds.append( fSelUVs[uvIndex] );
    }
    // Replace our local int array of UVIds
    //
    fSelUVs.clear();
    fSelUVs = validUVIds;
    // Build our list of valid components
    //
    MFnSingleIndexedComponent compFn;
    compFn.create( MFn::kMeshMapComponent );
    compFn.addElements( validUVIds );
    MObject component = compFn.object();
    // Replace the component list
    //
    MFnComponentListData compListFn;
    compListFn.create();
    compListFn.add( component );
    fComponentList = compListFn.object();
    return status;
}

splitUV コマンドの実装の詳細については、開発キットの plugins ディレクトリに提供されているソース コードを参照してください。