チュートリアル / 読んで触ってよくわかる!Mayaを使いこなす為のAtoZ
第79回:Mayaで複雑なツールを作る時のコツ ― 独自のコンポーネント

2019.07.02

  • Maya
  • ゲーム
  • スクリプト・API
  • チュートリアル
  • 中級者
第79回: Mayaで複雑なツールを作る時のコツ ― 独自のコンポーネント

前回は関数が使用するデータの受け渡しについてご紹介しました。今回はデータ自体の構成についてご紹介していきます。

シェイプのデータを取るのは大変!

Mayaでシェイプを扱うツールを作ることがあります。メッシュやらNURBSやら、Mayaにはいくつかのシェイプがあります。どれも3DCGらしいというか、たくさんのコンポーネントデータを扱う必要があります。頂点、エッジ、フェース、UV、座標、法線…全てひたすら数字の羅列です。

これらを使ってツールを作る時、メッシュから情報を引き出して処理を実行することになるのですが、情報がまとまっているように見えて、実はバラバラです。例えばメッシュのフェースとUVの情報を取りたいとします。OpenMayaで取るとすると、次のように書くことが出来ます。

メッシュのフェースとUVの情報を取りたいとき
# -*- coding: utf-8 -*-
import maya.api.OpenMaya as om
import maya.cmds as cmds

cube = cmds.polyCube()

# メッシュの選択リストを得て、MItMeshPolygonに設定。
sel = om.MSelectionList().add(cube[0])
dagPath = sel.getDagPath(0)
dagPath.extendToShape()
polyIter = om.MItMeshPolygon(dagPath)

# フェースを走査していきます。各フェースのUVを得ます。
while not polyIter.isDone():
    print ('face id: {}, '.format( polyIter.index() ) )
    u, v = polyIter.getUVs()
    print ('\t vtx index: {}, uv:{}'.format( polyIter.getVertices(), zip(u, v)) )
    polyIter.next(0) # Python API 2.0 のMItMeshPolygon.next()には、引数にNone以外の何かを設定しないといけません…。

フェースとUVの情報が必要になった時に、毎回このようにメッシュのイテレーターを作って、UVを取って来るのは大変ですよね。速度的にはそれほど問題になりませんが(C++では。Pythonは遅いかも)、コードを読むのが大変です。これは複雑なツールを作る時によく出くわす課題です。こんな時は、
コンポーネントごとに関連する情報をまとめておくと何かと楽です。

例えば、第77回のコラムでチョロっと出てきた、選択した一連のエッジ同士をブリッジするツールを作ることを考えてみましょう。

選択した一連のエッジ同士をブリッジするツール

エッジの情報を全てバラバラに持ってしまうと、毎回イテレーターやMFnMeshで情報を取ってくるか、次のように配列としてキャッシュしておくことになります。

edges = [[0,1,4,5,6], [2,3,7,8,10]] # 一連のエッジごとのインデックスリスト
targetEdges = [2,7,8,10,3] # 対応するエッジのリスト。1つ目に対する2つ目のエッジインデックス
normals [[(0.1, 0.2, 0.3), (0.15, 0.2, 0.36), ...], ...] # 何か法線情報とか

この見た目、なにか嫌な予感がしますねー。段々コードが訳わからなくなりそうな予感。そこで、 第78回のコラムで出てきたような構造体やクラスでまとめると、情報がわかりやすくなります。

class BridgeEdgeComponent:
    edgeId = 1 # 対象となるエッジのID
    targetEdge = 7 # 接続先となるエッジのID
    normals = (0.15, 0.2, 0.36) # edgeIdのエッジの法線
    ...

実はこれ、「新しいコンポーネントを作る」ことになります。

新しいコンポーネントの作成

メッシュなどシェイプのコンポーネント情報は扱いやすいとは言えません。ツールが何かを実行する時、コンポーネントの情報にアクセスしながら作業するなら、一度自分の使いやすいコンポーネントを作ることが重要です。メモリがもったいない気もしますが、Mayaの中やプラグインでも頻繁に行われている方法です。

新しいコンポーネントと言っても、単純に構造体やクラスにコンポーネントの情報や、ツールで必要な関連情報をまとめるだけです。これはMELだと出来ませんが、C++やPythonなら簡単に出来ます。一度自分独自のコンポーネントを作ってしまえば、コードの見通しも良くなりますし、関数に引数でデータを受け渡すのも簡潔に行えるようになります。なんとなくシェイプを加工するツールを作っている場合は特に、こうした新しいコンポーネントにデータをまとめると作りやすくなりますよ。もちろんコンポーネントだけではなく、オブジェクトと関連する情報をまとめたりすることもあります。

コラムの第77回~第79回で紹介した内容を踏まえ、「実行内容のステップ化」「関数を引数・メンバ変数で実行するさじ加減」「新しいコンポーネントの作成」の話を反映してブリッジツールを用意すると、次のようになります。

# -*- coding: utf-8 -*-
# (疑似コードです。実行しても何も起きません・・・。)

class BridgeEdgeComponent:
    '''
    ブリッジを実行するのに必要な、エッジをベースとした独自コンポーネントです。
    '''
    edgeId = 1
    targetEdge = 7
    normal = (0.15, 0.2, 0.36)
    ...


class BridgeEdgeTool:
    edgeComps = [] # BridgeEdgeComponentのリスト

    def __init__(self):
        pass

    def initializeBySelection(self):
        '''
        選択したエッジをもとに独自のコンポーネント情報を用意します。
        '''
        sels = cmds.ls(sl=1)
        ...
        edgeChains = self.getEdgeChains(sels) # 選択されているエッジから、一連のつながったエッジごとにリストを作ります。
        print edgeChains # [[1つ目の一連のエッジのリスト], [2つ目の一連のエッジのリスト]]

        # 2つの一連のエッジから、ブリッジ先として対応するエッジを一つずつ割り出します。
        self.edgeComps = self.buildBridgeEdgeComponent(edgeChains[0], edgeChains[1])

    def getEdgeChains(self, selections):
        ...

    def buildBridgeEdgeComponent(self, edgeListA, edgeListB):
        ...

    def doIt(self, division=0):
        # ここではエッジごとにブリッジを作りますが、edgeComps情報をもとに、いっぺんに作る方法も可能です。
        for edgeComp in self.edgeComps:
            self.createBridge(edgeComp.edgeId, edgeComp.targetEdge, edgeComp.normal, division )


    def createBridge(self, edgeID1, edgeID2, offsetDirection=(0,0,0), division=0):
        '''
        与えられた情報をもとに、ブリッジを作成します。
        '''
        ...

tool = BridgeEdgeTool()
tool.initializeBySelection()
tool.doIt(division=1) # オプションがあれば引数に指定

こういう書き方をすれば、後でコードを読む時も理解するのが簡単です。doItを見て、何をしているか確認。実行にはBridgeEdgeComponentという情報が必要だとわかります。この情報さえ用意できれば、シーンのデータをどう取ってきてもOKということなので、いくつかのコードはそもそも読まなくてもツールを理解できます。問題が起きた時は、BridgeEdgeComponentの中身を出力して確認すれば、意図しているデータが来ているかすぐに確認できます。データがおかしければデータを作っているinitializeBySelectionを確認し、データが正しければcreateBridgeを確認します。

この様に、独自のコンポーネントを用意すれば様々な面で効率も安全性もアップします。メモリの消費が増えても、それを補う利点があります。

まとめ

ツールによって必要な独自のコンポーネントは結構異なります。UVとUVから繋がっているエッジ情報を欲しい時もあれば、頂点と頂点のUV・エッジの情報が欲しい時もあります。出来ればコードを使い回せればよいのですが、付随する情報(上の例では法線情報)がツールによって変わってくるため、大概はツール毎に書き直すことが多いように思います。まあ、無理に再利用してコードがややこしくなったり、メンテナンスが大変になったりするよりは、それぞれで独自のコンポーネントを用意する方が良いです。

Mayaでの複雑なツールの書き方について、三回に渡ってご紹介致しました。Mayaでツールを作られている方のステップアップとして参考になりましたでしょうか?MELでツールを作っている方にとっては、Pythonを使ってみようかな?と思うきっかけになるかもしれません。今回ご紹介した内容はどれもMELでは出来ない部分がありますので、もしMELでツールを作っていて大変になってきたかな…と思うことがあれば、Pythonを使うときが来ているかもしれません!ぜひチャレンジしてみてください。

Maya Learning Channel
製品購入に関するお問い合わせ
オートデスク メディア&エンターテインメント 製品のご購入に関してご連絡を希望される場合は、こちらからお問い合わせください。