Maya Node referenceの不足を補う

皆さんごきげんよう。近々**[Maya道なる本](https://www.borndigital.co.jp/book/5764.html)**が出版されるようですね！[どうやら](http://melpystudio.blog82.fc2.com/blog-entry-205.html) (著者による紹介)によると mayaハカー御用達のmayaの根幹をなすところのテクノロジィである Dependency Node（DG), Directed Acyclic Graph(DAG)の解説がされるとのことです。よろこばしいことです！では翻って公式のドキュメント（http://help.autodesk.com/cloudhelp/2017/JPN/Maya-Tech-Docs/Nodes/index.html ）にあたりmayaのノードについて予習しておきましょう[^3]。もしあなたが単位変換マニアであるならすぐに気づくことでしょう。そう decomposeMatrix / eulerToQuat / quatToEuler について書いてないのです！なんということでしょう！この**レファレンスにはプラグイン由来のノードは一切含まれていない**のです！これではあのぱっと見超クールな MASHも Bifrostもスクリプトからノードで制御することも絶望的です。なんてこった *（もしプラグイン由来のノードについてのレファレンスの所在についてご存知の方いらしましたら教示いただきたい所存）* でも悲しむことはありません！**正式なものではないですが http://yamahigashi.github.io/maya-node-list/index.html に掲載**しています。これで**変換ホーダイ**です。やったー

[^3]: 予習するならこっちではなく http://help.autodesk.com/view/MAYAUL/2017/JPN/?guid=__files_DAG_Hierarchy_htm か http://help.autodesk.com/view/MAYAUL/2017/JPN/?guid=__files_Dependency_graph_plugins_htm を先に読みましょう

---

与太前置きが長くなりました、この記事ではどのようにしてノードの情報をスクリプト経由で引いてくるか、およびどのようにしてそれをドキュメントに書き出すかについて解説します。ドキュメントの生成を通じ、mayaのGUIを起動することなく目的の処理を実行する際の小ネタをお伝えします。ドキュメント生成に用いたすべてのスクリプト類は[こちら](https://github.com/yamahigashi/maya-node-list/tree/gh-pages/tool) にあります。（ただし行儀の 極めて悪い書き方を多用しているため注意してください。*2度見ることもなかろうとおもっててきとうにかいてしまった。*アイデアだけもっていってください）

## 概要

まずおおまかなドキュメント生成の流れは

1. mayapy.exe からノードの情報を reStructured Text(rst)で書き出す
2. 書き出した rst を [sphinx](http://docs.sphinx-users.jp/index.html) を使用し html に変換する

の2工程になります。

## バッチ起動

書き出しの実行は通常のmaya gui からも行うことができますが、ここではバッチ起動を行ってみます。mayapy.exe の利用については公式のドキュメントを参照してください。
http://help.autodesk.com/view/MAYAUL/2017/JPN/?guid=GUID-83799297-C629-48A8-BCE4-061D3F275215


### スクリプトの実行

コマンドラインからrst生成スクリプト をmayapy に食わせます ` C:\Program Files\Autodesk\Maya2017\bin\mayapy.exe dump_maya_nodes.py `
などとします。

処理の目的は **[ノードの情報一覧を書き出す]** です。では大量のノードをどのように扱うかを考えましょう。ノードの情報を書き出すにはまずノードを特定する必要があります。これには IDが使えそうです。ではIDはどのように取得すればよいでしょうか？ググる？いいえコンピュータに得意なことはコンピュータにやらせます。そうです０から数え上げていきましょう。[^1]  総当たりでノードの作成をおこないヒットしていたら処理続行、何もなければそのまま素通りです。[^2]

ここはどうせなので並列実行させてみましょう。 `multiprocessing` モジュールを利用します。

[^1]: この記事をしたためているうちに気付いが MPlugin からもしかして MTypeId ひいてこれるのではないだろうか
[^2]: 余談であるが MTypeId をどうするべきなのか全く分からない。くみこみのものでこれなんだから独自のノードプラグインを作成する際に何を基準とすればいいのやら・・・Autodeskにたのんでid発行？うーん


``` py:dump_maya_nodes.py
# https://github.com/yamahigashi/maya-node-list/blob/gh-pages/tool/dump_maya_nodes.py#L1265-L1294
def dump_id_range_using_multiprocessing():

    import multiprocessing as mp
    import itertools  # python 2.x can not iterate over 'long int' cause overflow error
    range = lambda start, stop: iter(itertools.count(start).next, stop)

    process_count = 4  # mp.cpu_count()
    po = mp.Pool(process_count)
    po.map(initialize_process, xrange(process_count))

    start = 0x30000000
    steps = 0x00001000
    end = 0x90000001
    ceil = 1e4

    while start < end:
        # print('start processing...', start)
        res = po.map_async(dump_node_by_id, range(start, start + steps))

        # wait a moment as the main process eat up huge memory
        # when runnig continuously
        if len(po._cache) > ceil:
            print(".", hex(start))
            res.wait()

        start = start + steps

    po.close()
    po.join()

    print('... done processing')
```

このようになります。

multiprocessing については割愛します。処理順にみていきましょう。まず `process_count` 分プロセスをpoolに用意します。用意したプロセスに対し初期化を実施、プラグインの読み込み等をおこないます。

```python
    po.map(initialize_process, xrange(process_count))
```
の行です。実態は

``` python
def initialize_process(*args):
    load_plugins()


def load_plugins():

    default_plugins = [
        "ik2Bsolver.mll",
        "objExport.mll",
        "dgProfiler.mll",
        "DirectConnect.mll",
        "quatNodes.mll",
        "matrixNodes.mll",
        "ikSpringSolver.mll",
        "animImportExport.mll",
        ...
    ]

    def _l(name):
        try:
            # print "load plugin load: ", name
            cmds.loadPlugin(name)
        except:
            pass
            # print "pass plugin load: ", name

    for n in default_plugins:
        _l(n)
```

このようになっています。mayapyを起動すると無垢の状態で生まれてくるため必要なものの読み込みを行います。ここではプラグインの読み込みだけですが、モジュール読み込みなど、処理に応じて必要があれば記述しましょう。



処理開始時点の id とステップ数、終端id を定義しループを回しその内側で`po.map_async` で並行処理を実施します。この用途のように大量に処理させる場合、素のままガン回し ― つまりこのコードに即して言うとwhileループ外で`map_async(dump, range(start, end))` としてしまうとメモリ圧迫で死んでしまうため、適当なステップを区切りさらに一定まで進んだら `res.wait()` をいれています。無尽蔵にマシンリソースを食い尽くすことをさけます。最適な step数と ceil は状況によって異なるため若干の試行錯誤が必要です。


また process_count をマシンのコアに合わせてしまうと実行している間何もできないので余裕を見るとよいでしょう。完了まで1時間かかります。


### ノードの情報を取得する

ここでは

```python
# https://github.com/yamahigashi/maya-node-list/blob/gh-pages/tool/dump_maya_nodes.py#L43-L91
def dump_node_by_id(raw_id):
        id = om2.MTypeId(raw_id)
        nc = om2.MNodeClass(id)

        typ = om2.MFnDependencyNode()
        obj = typ.create(id, 'test')
        dpn = om2.MFnDependencyNode(obj)

        attributes = {}
        for num in xrange(ac):
            a = om2.MFnAttribute(dpn.attribute(num))
            k, v = inspect_attribute(dpn, a)
            attributes[k] = v
```

としてノードを作成し、そのアトリビュートを探索しに行っています。`inspect_attribute()` についてはコードを参照してください。

### rst に書き出す

書式がきまっているものを書き出す場合、なんらかのテンプレートエンジンを用いると便利です。ここでは Jinja2 （神社＝てんぷる）を利用しました。そのため maya python から見えるところにモジュールを配置しておきましょう。ノードのアトリビュート探索結果をテンプレートに適用し生成します。
https://raw.githubusercontent.com/yamahigashi/maya-node-list/gh-pages/source/_templates/node.tpl.rst テンプレートはこう。

## Shpinx

ここまで用意できれば後は Sphinx を実行するだけです。 `make html` を実行します。

**注意**
sphinx プロジェクトを用意していない場合、rst生成より先にsphinxプロジェクトの作成、設定を済ませておきましょう。http://sphinx-users.jp/gettingstarted/make_project.html

## おわりに

いかがでしたでしょうか。駆け足でしたが maya のノード一覧ドキュメントの生成についてみてきました。この記事をご覧の各人がそれぞれノード一覧を生成したい！などということはありえそうもないですし、仮にあったら世のなか何か間違ってます。しかし、maya に限らずドキュメントに記載されていない情報が必要となることは往々としてよくあります。そのような場面に遭遇した際、ここで解説したエッセンスがなんらかのお役に立てれば幸いです。この記事には登場しませんでしたが Python であれば 組み込み関数 `help()` (@sporty さんの記事にも[登場](http://qiita.com/sporty/items/681aa8bbeac81c71c518)しましたね), `dir()`や `inspect` モジュールにより既存コードの分析ができます。C# であれば reflection などが利用できます。ドキュメントを探す能力も非常に重要ですがそれだけではダメな時にこのような手段があるということを頭の片隅に置いておくといつか役立つでしょう。

---