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.

テクスチャやマテリアルのリストを取得する

fileノードとかそういうのを取得するのに、pluginによるノードとかどうやって集めれば??というのが、前から疑問だったので調べてみた。
要点としては、「HypershadeのTextureタブに列挙される要素の取得がしたい。」という話です。

結果としては、以下のコマンドで取得できた。

こっちは、マテリアル。

そのほかのタブ

ユーティリティ:DefaultRenderUtilitiesFilter
レンダリング:DefaultRenderingFilter
ライト:DefaultLightsAndOpticalFXFilter
カメラ:DefaultCameraShapesImagePlanesFilter
シェーディンググループ:DefaultShadingGroupsFilter
ベイクセット:DefaultBakeSetsFilter
アセットノード:DefaultContainerNodeFilter

Maya起動用ランチャー

少し前にMayaをコマンドラインから起動する件を投稿したけれど、なかなかコマンドラインやら環境設定やら難しい…という場合も多そうだよな。と思ったのでググってみたら公開している人がいらっしゃった。

arubertoson/maya-launcher

Pythonで単純に設定を管理するタイプのコマンドライン・スクリプトだけど、要はこういう話なので参考になるかも。

過去の投稿

VSCodeの環境を入れる

この辺かな~って感じのメモ。

会社ではVSCode利用者増えたし、そろそろ重い腰を上げてみようかと思ったり。。
※今回は、基本インストール不要のポータブル環境を検討。

Mayaのシーンが表示されるパネル

モデリングしたりレイアウトする時に使うパネル。Helpでは、まずビューと呼ばれている領域。
“modelPanel”というコマンドで用意出来るんですが、作る以前にデフォルトで存在しているこのビューの領域をスクリプトからどうやって弄るのか?というのは、結構疑問だったりする訳です。

まず「getPanel -wf」。
直前にフォーカスを持っていたパネルが取得できます。perspビューでオブジェクトを作成して、その後スクリプトエディタを開いて「getPanel -wf」を実行すると、perspビューのパネル名称を取得できます。(スクリプトエディタのパネルにフォーカスが移動するので、都合直前にフォーカスを持っているのはperspビューのパネル。という事になります。)

「getPanel -type “modelPanel”」を実行すると、下記のようになる。

 
4枚modelPanelが存在するという事です。実際、「top, front, side, persp」があるので4枚です。

modelPanel -q -l “modelPanel4″」を実行すると「// Result: Persp View //」が返ります。

逆に「getPanel -withLabel “Persp View”」を実行すると「// Result: modelPanel4 //」が返ります。

「modelPanel -q -cam “modelPanel4″」を実行すると「// Result: persp // 」が返ります。
※同様に「modelEditor -q -camera “modelPanel4″」でも「// Result: persp // 」が返ります。

予め「camera1」を作成しておいてから、「modelPanel -e -cam “camera1” “modelPanel4″」を実行すると、perspビューは「camera1」からの視点に変更されます。
同じことは「lookThru modelPanel4 camera1」でも可能です。(modelPanel4 –> perspView 置き換え可能。)

lookThruコマンドは、ビューへのカメラ設定専用コマンド。
ニア/ファー・クリップの値設定も可能。

 
先ほど少し触れた「modelEditor」コマンドを使うと、ビューの表示制御もできます。
予めシーンに「cube」を作成しておき、「modelEditor -e -displayAppearance “flatShaded” “modelPanel4″」を実行すると、画面表示がシェーディングモードに変更されますし、「modelEditor -e -displayTextures on “modelPanel4″」だと、テクスチャ表示がONになります。

「cube」を選択して「viewFit」コマンドを実行するとcubeが画面に収まるようにカメラ位置が調整されます。
viewFitは、アクティブなビューに対するコマンドなので、スクリプトで実行する場合には事前に対象のビューをアクティブに設定する必要があります。(例えば、perspビューならば「setFocus modelPanel4」を実行)

コマンドによって、オブジェクト名(modelPanel4)ではなくラベル名(“Persp View”)で指定できるが、コマンドにより異なるので留意。

Mayaをコマンドラインから立ち上げる

前回ちょっとしか書かなかったので、もうちょっと追加。

自動実行のmelを利用するには、

拡張子”.mel” のファイルを作成して、その中にmelで実行したい処理を列挙していけばOK。

もしバッチ実行ではない場合は、実行する処理の最後に quitコマンドを記述しておくことで、自動的に終了させることも可能。
但し、usersetup.melやusersetup.pyで起動時に独自に起動処理を設定している場合や、起動直後に新機能などを表示するダイアログを自動表示させる設定になっている場合には終了に失敗する場合があるので、まずはダイアログを自動表示させる設定はOFFにしておいて欲しい。
起動処理を設定している場合は使用しないように対処したり、evalDeferredコマンドを使用して、自動実行したい処理を遅延実行させるような対応が必要になる。

上記のように、バッチ実行ではない場合には自動終了時に面倒が起こる場合があるので、GUIや画面表示が存在しなくてはならない処理を実行する必要がない場合以外は、基本バッチモードでの実行が無難。(但し、playblastなど画面表示の機能が稼働していないと動作が得られないものもある点に留意。)

そういえば、参考サイトのサンプルのバッチファイルの記述では、Maya.exeを起動した場合にはスクリプトの自動実行を行わないような内容になっているが、MayaもMayaBatchもその辺りの設定項目は同じなので、通常のMaya起動からの自動実行もバッチファイルを少し変えてやれば可能になる。
一番最初のハードルは

環境設定群だと思う。
とても基本的な部分なのだが、通常は特に設定を変更する必要なく使えてしまう部分な上、変更すると使えていたものが使えなくなってしまい、元に戻せなくて焦る…といったことも起こりやすい。
この部分を試すにしても、冒頭の参考サイトにあるバッチファイルによる設定が活躍する。
バッチファイルで実行するぶんには、Windowsに直接設定されている内容を汚すことはないので、存分に設定を弄り倒すことができる。(但し、既にWindowsに設定された内容を引き継いでしまう部分があるので、逆に設定を消すようなバッチファイルの記述が必要になる場合がある点には留意。)

環境設定の学習が進めば、複数の環境を切り替えながら使うことも出来るようになるので、頑張って習得するのが良いと思う。

追記

maya.envにて、環境設定をカスタマイズできる事を知らなかったもので追記しておく。

Maya起動用バッチ

説明している人が居たので、メモしておく。

Maya起動バッチを作ろう@0303

コマンドラインからMaya起動する方法を知っていると、環境を複数用意することも可能になるので、特に開発を行っている人に必要になる知識。知っておくと、環境をキチンと分けられるので無用な問題も減って開発しやすいと思う。
※ただ、キチンと分けない人の環境で発生する問題に気付くことには疎くなる…という別のデメリットもあるけど。。

Maya終了時のスクリプトログを得るには。

そもそも、どうやって取るのか全然知らないので、ちょっと調べてみた。
方法はいろいろあるかもしれないが、環境変数”MAYA_CMD_FILE_OUTPUT”で指定したファイルにログを出すことができた。

予め上記のように環境変数に出力ファイルを指定しておくと、そのファイルにスクリプトログの結果が出力される。
Mayaが閉じる場合に実行されるスクリプトのログを取らないといけない場合には、こういう手段でもないと状況が判らない…

追記

上記はコマンドプロンプトからMayaを実行する場合の話なので、Mayaアイコンのダブルクリックで実行する場合にはWindowsのシステム設定の環境変数として”MAYA_CMD_FILE_OUTPUT”を登録する必要がある。

辿り方は2通り。
・コントロールパネル(アイコン表示時) > システム > システムの詳細設定 > 環境変数
・コントロールパネル(カテゴリ表示時) > システムとセキュリティ > システム > システムの詳細設定 > 環境変数

コントロールパネルには、表示方法が「カテゴリ」と「アイコン」の2通りがあるので、設定により辿る際のパスが違う点に留意。
※デフォルトでは、カテゴリ表示。
追記(その2)

掲題の目的とは別に、スクリプト実行時のログをファイルに残したい場合には「cmdFileOutput」というコマンドがあった。
こちらは自分でログファイルのopen/closeを指定する必要があるので終了時に使うようなコマンドではないが、特定のコマンドを実行する際にのみログを取りたい場合には、直前・直後にopen/close操作をすることで特定の処理のログだけをファイルに出力できる。

ファイル操作と同じくcloseを明示的に行う必要がある為、その点には注意が必要。(特に、例外発生でclose処理がskipされることがないように留意する事。)
その後

 MayaやMayaBatchの起動スイッチに -log というのがあって、これでもログをファイルに記録することが可能だった。

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

unload状態で、referenceノードだけを作成する

仕事場で、出来るのかな?って話が出たけど、昔やった時のフラグ設定とか忘れてたので再度調べてみたり…

[PySide] Maya、Windowsスタンドアロンの記述

少し記述に違いがあったり、モジュールとか環境の違いも出るので、メモが必要な感じ。

極端に古い環境を考えても仕方ないけど、そもそも自分も結構古い環境なので、現行との違いとか忘れてしまうのもある…

2014/2016/2018で考えてみたい。

PySide/PySide2

PySide1: 2014, 2016
PySide2: 2018(※2017から、PySideのバージョンが変わってる)

shiboken

2015/2016辺りでMixin記述が出来るようになったので、2016, 2018はshiboken不要。
2014の場合はshiboken入れるなり、MayaのWindowハンドルを取得するなりして、PySide側のWindowにParentの設定が必要。

QApplicationのinstance生成

これは、スタンドアロンとMayaの場合で実装が異なる。
スタンドアロンの場合は、よくあるexampleのようにインスタンスを生成すればいい。

Mayaの場合は、既にインスタンスが作られた状態から動かすので、必要に応じてinstanceメソッドでインスタンスを取得する。

でも既にイベントループとか動いているので、app.exec_()しなくてもウィンドウ作成&表示で動き始める。
必要なら呼べば大丈夫な感じ(そういえば、これはOKなのか?)ていうか、要らない。

[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) で終わらせるとおかしなことになってしまうので、注意したい。

self.sender() で発信元が見えない場合

 自分は、self.ui=loader(path)でui読み込んだ後、gui.ui.show() とかやっていたが、これではsender()で発信元を受け取れなかった。
 仕組み的に、ちゃんとQMainWindowにQDialogとか読み込んで配置するには、正しい場所にuiを配置しないとダメだった…ということです。

関連:シグナルの発信元を取得する