PythonでMayaのPlugin作るのは

API2.0を使った方がいい。速度が違うので…
前回の記事は旧APIを参考にしていたのだけど、検索で引っ掛かったのが旧APIだったので…

これからPluginをPythonで書くなら、API2.0を使おう。

ヘキサドライブさんの記事が参考になると思う。
API2.0も旧APIも基本C++のラッパーなんで、基本的な所は同じ。
ただAPI2.0の方が、よりPythonっぽい書き方で実行速度も改善されてる。(ものによって数倍速いみたい)

MayaPluginをPythonで記述

めも。

記述の参考。

https://download.autodesk.com/us/maya/2009help/API/zoom_camera_cmd_8py-example.html
https://download.autodesk.com/us/maya/2009help/API/motion_trace_cmd_8py-example.html

たぶんplugin1つに複数コマンドを内包させるのは、registerCommandを複数呼ぶだけだと思う。

その後

試しに2つ定義してみたら、別々の実装を処理しに行った。
なので、複数コマンド用にクラス定義で実装を増やせばOK.

Maya Python Pluginのこと

 すっかり忘れていたけど、Maya PluginはPythonでも記述できる。
 Pythonはインタプリタなので、単純操作での処理速度は(特に制限なく並列処理も可能な)ネイティブコードを実行できるC++に敵う筈もないけど、何も考えなくても基本的な処理の流れがある程度最適化された形で実行されるPythonも状況によっては遜色なく使える。
 ※特にMayaはごく最近のバージョンまで並列処理をなかなか安定的に処理できなかったし。。

MayaはParallel Evaluation(並列評価)という仕組みが2015か2016で実装されて以来、いままで少しずつ機能が整えられてきていて、2019辺りではようやく使えるかな…という気配も見えてきた気がする。
並列処理が難しいのは順不同で評価が始まる部分。評価順序に優先度がある場合には意図しない結果に繋がるので、まず評価に順序があるのかないのかをよく考える必要がある。

 
 今更C++を思い出そうとしていたけど、新しいPCには光学ドライブすら付いてなくてVisualStudioのインストールにも骨を折らないといけない状況なので、Pythonでやってみようかと思い始めたり。

 これはMaya2017ヘルプでの記述だけど、ざっくり言ってC++で書いていたような記述をPython形式で記述してMayaのPluginフォルダに入れてやれば、PythonでMayaのPluginを用意できる。

 DFTalkに記事があったので、これも引用しておく。

MayaのPluginを書くには、オブジェクト指向についての知識が必要になる。
「オブジェクト指向とはなにか?」については、簡単に説明することがとても難しい概念だけど、(オブジェクト指向とレスポンシビリティで)以前触れたことがある。このあたりに関しては書籍などもあたってみて、正しい知識を仕入れて欲しい。

[Maya] ノードの変更を検出してツールの値を更新

 掲題の通り、Maya向けにアトリビュート編集をするUIをPySideで作っている時に、MayaでもPySideのUIどちらからも値の変更ができるようにする処理を書いていて、何故かMayaがCrashする現象を起こしてしまって何故だーと思っていたら、単にシグナルのブロックが不完全だった…というお話。

 やってるつもりでもポカしている部分はあるもので、油断は禁物。という話になってしまった。

ノードの変更を検出

 これは、先に書いた記事(ノードの変更を検出する)を参照すれば基本的なことは分かると思う。
 指定のノードに変更が掛かると、コールバック関数が呼ばれる。
 コールバック関数内でなんの変更が掛かったか判定して、必要に応じて対応をする。単に値の変更を捕まえるなら、MNodeMessage.kAttributeSetをチェックすればいい。

 問題なのは、MayaとPySide2つの変更に対応する場合にどう書くか。

 基本的にコアな部分は、この2通り。

 ・Maya(getAttr)⇒PySide(set)
 ・PySide(get)⇒Maya(setAttr)

 イベントのトリガーは、基本2つ。
 (※Mayaのタイムライン移動にも対応とか諸々対応すると増えるが、とりあえず値変更に限定した場合)

 ・Maya : ノードの変更を検出
 ・PySide: widgetの変更イベント

 動作としては、以下の通り。

 ・Mayaトリガー
  ノードの変更コールバック処理で、
  Mayaの対象ノードをgetAttr、PySideのwidgetにsetする。

 ・PySideトリガー
  PySideのwidgetの値更新イベント処理で、
  PySideの値をget、Mayaの対象ノードにsetAttrする。

シグナルの抑止

 これだけで書くと、冒頭に書いたCrashの件のような問題が起こる。
 単純に、Mayaトリガーの変更を実装。PySideトリガーの変更を実装。
 …というように進めてみるとふと気づくのが、「変更イベントが循環する」という問題。

 PySideでGUIのプログラムで凝ったものを書き始めると途端に出てくる障壁のような問題なのだが、これを制御しようとし始めると「シグナル」の機構を理解しておく必要が出てくる。
 その機構の利用方法の1つが、blockSignalsというメソッドの機能。

 このメソッドを呼ぶと「シグナルの抑止動作」を制御してくれる。
 単に止めるのではなく、止めるか止めないかを設定できるので、必要に応じて一時的に止めてその隙に変更を掛ける。ということができるようになる。

 これを然るべき位置に設定しておけば、(無限ループに嵌って)Crashすることなく意図通り動いてくれるようになる。

循環しない形

 先ほどの処理の話で言うと、循環が起こると例えば以下のような状況が発生する。

 ・Mayaトリガー
  ⇒PySideのwidgetに値をset
  ・PySideトリガー
   ⇒Mayaのノードに値をsetAttr
   ・Mayaトリガー
    ⇒PySideのwidgetに値をset
    ・PySideトリガー
     ⇒Mayaのノードに値をsetAttr
     :
     :

 このように、シグナルを止めなければ循環してしまう。
 なので形としては、以下のようにしなければならない。

 ・Mayaトリガー
  ⇒flg=blockSignal(True)
   PySideのwidgetに値をset
   blockSignal(flg)

 ・PySideトリガー
  ⇒flg=blockSignal(True)
   Mayaのノードに値をsetAttr
   blockSignal(flg)

 PySideトリガーの場合、Maya側のトリガー(ノードの変更通知)をどこで抑止するかにもよるが、blockSignalsメソッドを利用するにはシグナルの機構を自前で用意する必要がある点に注意。
 Pythonは基本マルチスレッド動作ではないので、(並列処理が絡まなければ)単にフラグ管理でも動くだろうし、ちゃんとシグナルを使って記述するもよし、である。

 それからblockSignalsメソッドを使う場合は、flg=blockSignal(True) してから blockSignal(flg)で終わらせるのが基本的なお作法。
 これは、blockSignalsが“入れ子”になってしまう場合を想定した書き方になる。

 途中のメソッド内が、blockSignal(False) で終わらせるとおかしなことになってしまうので、注意したい。

ノードの変更を検出する

調べたら意外と最近追加されてたので、めも。

マニピュレータで移動している間、任意のスクリプトを実行するスクリプトのサンプルはありますか(@AUTODESK KNOWLEDGE NETWORK)

インスタンスの破棄について

 基本的には、2通り。
 ・「del instance」の形で明示的に開放する
 ・関数などのブロックから出ることで暗黙的に開放する

MayaAPIを叩くと undoは効かない

MayaAPIを直接叩いて編集をした場合、その操作についてのundo操作は無効になる。
undoを行うには、コマンドプラグインを書きしかるべき対応を行う必要がある。

オブジェクトの作成と操作(@Maya2016help)

DAG操作時はこれで、編集操作を覚えさせる。

DG操作時はこっちを使う。

…で、Mayaのコマンドの仕組みに乗っかって Undo/Redo 操作時に呼ばれる undoIt/redoItメソッドで、ModifierオブジェクトのundiIt/doItメソッドを呼び出すと、実際にその操作が行われる。

MFnDependencyNodeでオブジェクト作成

かなり久々にやろうとしたら凄くまごついたのでメモ。
Maya Python API 2で書いてみた。

componentScaleManip.cpp を ~.pyに書き換えてみた

 どういうわけかdevkit内のサンプルに vertex編集関連のマニピュレータ pluginのPython版が見当たらなかった(入っているmoveManip.pyは、コンポーネントの編集に非対応だった)ので、試しにC++のコード(componentScaleManip.cpp)をPythonに書き換えてみた。
 元のコードは サーフェースのCV編集をする実装なので、meshのvertex, pnts基準で編集するように書き換え。
 エラーチェック処理の端々まで動作確認していないので、変なところはDo It Your self で。
続きを読む componentScaleManip.cpp を ~.pyに書き換えてみた

近傍頂点の検索

>get closest vertex in maya using python(@djx blog)
http://www.djx.com.au/blog/2013/07/07/get-closest-vertex-in-maya-using-python/

 手続き的な部分が判りづらかったり、pymel使わない人だったりしたので、書き換えてみたが。

 しかし、かえって判りづらいという結果に..

MStatusの扱いについて

完全に忘れてたのでメモ。

>”例外と MStatus” の項を参照 (@Maya 2013 Help, API ガイド > Maya Python API > Maya Python API 1.0 > Maya Python API を使用する)
http://docs.autodesk.com/MAYAUL/2013/JPN/Maya-API-Documentation/index.html?url=files/Maya_Python_API_Using_the_Maya_Python_API.htm,topicNumber=d30e13074

Pythonでは MStatusを用いない, 代わりに、いくつかのルール付けがある。

C++ API メソッドが MStatus 値を返す方法には、2 通りの方法があります。

メソッドの戻り値として返す方法

パラメータ リストの MStatus 変数(通常、最後のパラメータ)へのオプション ポインタとして返す方法

メソッドが関数値として MStatus を返す場合
 メソッドが関数値として MStatus を返す場合、この戻り値は Python で次のように処理されます。

  ・ステータスが MS::kUnknownParameter である場合、文字列 unknown が Python に返されます。
  ・ステータスが MS::kSuccess である場合、何も返されず、例外も発生しません。
  ・それ以外のステータスの場合、何も返されませんが、RuntimeError 例外が発生します。

 MS::kUnknownParameter を特別に処理するのは、MPxNode::compute() に対応するためです。
 API 固有の特別な例外はありません。Maya は単に Python の標準 RuntimeError を使用し、エラー文字列を引数として渡します。

メソッドがポインタ変数を通じて MStatus を返す場合
 API メソッドがパラメータ リストのポインタ変数を通じて MStatus を返す場合、この MStatus は次のように処理されます。

  ・ステータスが MS::kSuccess である場合、例外は発生しません。
  ・それ以外のステータスの場合、何も返されませんが、RuntimeError 例外が発生します。

 つまり、C++ 言語でプラグインを記述する場合に、コードを呼び出しているのが C++ であるか Python であるかに関係なく、これまでどおり MStatus コードを返すことができます。Maya は必要に応じて、これらのコードを Python 例外に変換します。

 Python でプラグインを記述する場合は、MStatus 値を返すのではなく例外を発生させる必要があります。ただし、compute() メソッドでプラグを処理しないことを指示する場合を除きます。この場合、このメソッドは maya.OpenMaya.kUnknownParameter を返します。

MGlobal::getSelectionListByName

やったことめも。

これを使えば、別にアクティブなセレクションリストに関係なくセレクションリストを得られる。

kMeshEdgeComponentを取得するサンプル

やったこと、めも。

API での選択の概要 / シーン グラフを照会する

>API での選択の概要
http://docs.autodesk.com/MAYAUL/2014/JPN/Maya-API-Documentation/index.html?url=files/Selecting_with_the_API_Overview_of_selecting_with_the_API.htm,topicNumber=d30e8341

・通常、コマンドはセレクション リストから入力を取得します。MGlobal::getActiveSelectionList() メソッドの結果は、選択されたすべてのオブジェクトを含み、MSelectionList と MItSelectionList(セレクション リストの編集に使用できる 2 つの API クラス)によって簡単にチェックすることができます。

>MGlobal::setActiveSelectionList()
http://docs.autodesk.com/MAYAUL/2014/JPN/Maya-API-Documentation/index.html?url=files/Selecting_with_the_API_MGlobalsetActiveSelectionList.htm,topicNumber=d30e8373

・グローバルなアクティブ セレクション リストは、以下を使用してコピーできます。

・以下を使用しなければ、MSelectionList メソッドで行った変更は、グローバル リストに反映されません。

>シーン グラフを照会する > 選択したシーン エレメントの検査
http://docs.autodesk.com/MAYAUL/2014/JPN/Maya-API-Documentation/index.html?url=files/GUID-0B85C721-C3C6-47D7-9D85-4F27B787ABB6.htm,topicNumber=d30e6386

・Maya のユーザ インタフェース(またはスクリプト)を使用してオブジェクトを選択すると、オブジェクトがグローバル アクティブ セレクション リストに追加され、MGlobal.getActiveSelectionList() を使用してアクセスできるようになります。

MGlobal::selectByName

http://docs.autodesk.com/MAYAUL/2014/JPN/Maya-API-Documentation/index.html?url=files/Selecting_with_the_API_MGlobalselectByName.htm,topicNumber=d30e8690

以下のような記述で、パターンが一致するすべてのオブジェクトが検索され、アクティブ セレクション リストに追加される。
※結局のところAPIで書く場合、MGlobal::getActiveSelectionList() は使用する。

以下は、Pythonで幾つかテスト。

Python向けの OpenMayaモジュールについて

ライブラリごとにモジュールは分かれている。忘れっぽいので、めも。