Posted at

Maya Node referenceの不足を補う

More than 1 year has passed since last update.

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


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


概要

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


  1. mayapy.exe からノードの情報を reStructured Text(rst)で書き出す

  2. 書き出した rst を sphinx を使用し 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はどのように取得すればよいでしょうか?ググる?いいえコンピュータに得意なことはコンピュータにやらせます。そうです0から数え上げていきましょう。2 総当たりでノードの作成をおこないヒットしていたら処理続行、何もなければそのまま素通りです。3

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


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に用意します。用意したプロセスに対し初期化を実施、プラグインの読み込み等をおこないます。

    po.map(initialize_process, xrange(process_count))

の行です。実態は

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時間かかります。


ノードの情報を取得する

ここでは

# 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 さんの記事にも登場しましたね), dir()inspect モジュールにより既存コードの分析ができます。C# であれば reflection などが利用できます。ドキュメントを探す能力も非常に重要ですがそれだけではダメな時にこのような手段があるということを頭の片隅に置いておくといつか役立つでしょう。






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



  2. この記事をしたためているうちに気付いが MPlugin からもしかして MTypeId ひいてこれるのではないだろうか 



  3. 余談であるが MTypeId をどうするべきなのか全く分からない。くみこみのものでこれなんだから独自のノードプラグインを作成する際に何を基準とすればいいのやら・・・Autodeskにたのんでid発行?うーん