Maya で camera や object が動いたらコールバック関数を呼んでデータをネットワークで飛ばしたい(python)


## 背景

Maya でのカメラやマテリアルの変更などをネットワーク経由などで飛ばして, 別の 3D ツールに反映させたい.

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ねんがんの Maya blendshape weight -&gt; <a href="https://twitter.com/hashtag/glTF?src=hash&amp;ref_src=twsrc%5Etfw">#glTF</a> morph target weight 転送(JSON-RPC over HTTP) に成功したぞ!!! 🎉🎉🎉🎉🎉💯💯💯💯💯💯😍😍😍😍😍😍💪💪💪💪💪💪💪💪 &gt; Maya で camera や object が動いたらコールバック関数を呼んでデータをネットワークで飛ばしたい(python) <a href="https://t.co/izRUPRkcsK">https://t.co/izRUPRkcsK</a> <a href="https://t.co/nFGZ7qgAjm">pic.twitter.com/nFGZ7qgAjm</a></p>&mdash; Syoyo Fujita 🌸 RayWa(Ray and Peace) (@syoyo) <a href="https://twitter.com/syoyo/status/1156901189096624128?ref_src=twsrc%5Etfw">August 1, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

## 方法

Maya(~2018) では, 主に二つの手段で python でコールバックをしかけることができます.

* scriptJob : play 時や render 時にコールバックを呼ぶ https://download.autodesk.com/us/maya/2011help/CommandsPython/scriptJob.html
* MNodeMessage : dependency node でのプロパティ変更時 https://knowledge.autodesk.com/ja/support/maya/troubleshooting/caas/sfdcarticles/sfdcarticles/kA23A000000hdtH.html

カメラを追加したときに MNodeMessage のコールバックをインストールするとか, 再生フレームごとにコールバック関数を呼ぶのは scriptJob, カメラ位置が変更したときに callback 関数を呼ぶのは NodeMessage でしょうか.
 
## MNodeMessage

MNodeMessage でのコールバックは, attribute が変わるごとに呼ばれます. たとえばマウスドラッグでカメラや物体を移動している間ずっとコールバックが呼ばれます. 一方, scriptJob ですとマウスが離れたときだけコールバックイベントが発生します.

マウスを離した時だけなにか処理をしたい場合は, mouse の dragging ステートを取得するのがよいのかな? 単純に translate, rotate, scale あたりの変更だけコールバックで取得したいのであれば scriptJob でもよさそうです.

## scriptJob

scriptJob は同じイベントに対して複数呼ぶと, 複数回コールバックが呼ばれてしまいます.

一個のイベントだけ追加したい場合は, `cmds.scriptJob(listJobs=True)` で該当する attribute を探して kill するコードを書く必要がありそうです.

### scriptJob のコールバックから呼ばれたオブジェクトの情報を得たい

イベントで呼ばれるコールバック関数を共通化したいときがあります.

python lambda あたりで, attribute 名文字列をコールバック関数に渡すのはできそうですが,
この場合は MNodeMessage を使うのがよさそうです. 

## ネットワークで飛ばす.

mayapy(python2) では, 標準で http(urllib), https(urllib2), JSON(json) が使えますので, カメラの情報など軽量なメッセージは JSON-RPC などにして扱うのがよいでしょう.

https://www.jsonrpc.org/

Python には標準で base64 モジュールもありますので, データ量が少なければ, バイナリデータを BASE64 変換して JSON-RPC で送るということもできます.

より高度なことや, バイナリでデータ転送をしたければ, 別途 websocket や, RPC ライブラリなどをインストールして使うことになります. 

ただ, Maya の python バージョンは 2.x になりますので, 新しめのライブラリですと python2 には対応していないかもですので注意ください(将来の Maya で python 3.x に対応するかもですが, 既存 Maya との互換性の問題から難しそう...)

MNodeMessage でマウスドラッグ時でもメッセージを送りたい場合, 後述する redis や etcd などの kvs(イベント処理サーバとして利用)を経由するとよいでしょう.
Maya 側からの送り手は送り手の都合(タイミング)でデータを送信でき, 受け手(3D ツール)でも受け手の都合(タイミング)で redis/etcd からメッセージを pop することでそのときの最新のデータを取得することができます. 

同一ネットワーク内で, 3D ツールも python に対応していれば, pickedb を使うでもいいかもしれません.

https://pythonhosted.org/pickleDB/

### Windows で localhost を使うと遅いかも

Windows で, アドレスに localhost を使い, urllib を使うと, DNS クエリが発生して遅くなるケースがあります.

https://stackoverflow.com/questions/32505410/python-requests-module-is-very-slow-on-specific-machine

ローカルで動かすのであれば, `http://127.0.0.1` を使いましょう.

## その他の手法

camera などの情報を常に polling する thread を立てるという手も考えられます.
シーンデータが重くなければ, polling ベースというのも手でしょう.

## おまけ

### python スクリプトを効率的に開発, 実行する.

https://qiita.com/dockurage/items/cc4d56a61da9220e984f
https://qiita.com/takumi_akashiro/items/5e18dd96b7af942cefbc

commandPort 経由で python スクリプトを送ることで, VSCode などで python コードを編集して開発できます.

ただし, VSCode のものだと PySide をインポートするようなコードを送るとおかしくなりました.

`execfile()` でファイルを eval するコマンドを送るようにしたほうが安定して動作しそうです.

また, Maya 上で, HotKey editor で python コードをショートカットキーに割り当てることができます.
私は `CTRL + m` を使いました.

ただし, Script Editor window ではカスタムのショートカットキーが効かない & `CTRL + m` は delete tab に割り当てられていますので注意ください.

### Maya へデータを取り込む

https://github.com/johnjcsmith/iPhoneMoCapiOS

iPhone mocap のように, 他のキャプチャデバイスや 3D ツールなどから, Maya へデータを供給する方法を考えてみます

Maya(mayapy)で http サーバを構築することもできますが, 仕組み上は redis や etcd などのサーバを立てておき, キャプチャ側はそのサーバへデータを(REST で)送る, maya はデータを redis/etcd などのサーバから REST で取得する, がよいでしょう.