また前回の更新からしばらくあいてしまいました(汗)
これは Maya Python
Advent Calendar 2017 の17日目の記事です。
ちなみに前日は@sho7nokaさんの「Maya と PyCharm の設定を晒す」、
翌日は@minoue2さんの「PySideのqcompleterを使ったタブメニューもどき」です。
ゲームのモデルでは、頂点カラーをそのままカラーとして使わずに
RGBAを別々に何かの制御用に仕込んでシェーダーで係数として使うというのをよくやります。
※例えば、下記の記事で書かれているギルティギアの場合、輪郭線の制御まわりに使われています。
西川善司の「試験に出るゲームグラフィックス」(1)「GUILTY GEAR Xrd -SIGN-」で実現された「アニメにしか見えないリアルタイム3Dグラフィックス」の秘密,前編 - 4Gamer.net
しかし、そうした場合にMayaのビューポート上でRGBAが混ざった状態だと、
各チャンネルの強弱がどうなっているのか分かりづらいです。
そのため、以前から作ろうと思っていたのですが、ちょうどMaya Python Advent Calendar 2017が作られたときに
@unpyside さんからお誘い頂いたのでこちらのネタとして作ってみました。
ファイルはこちらにアップしてあります。(初めてGitHub使ってみたのでまだ良くわかってないですが…)
コードはこの記事では全部載せていませんので、GitHubの方でご確認下さい。
Maya2016と2017では確認していますが、
使用にされた際に不具合が出ても責任は取れませんので自己責任でお試し下さい。
まず実行の仕方です。
GitHubのページの「Clone or download」ボタンからZipファイルをDLしてもらい、
それを展開してもらうとscriptsフォルダの中に
・kkDisplayVertexColorSeparately.mel ファイル
・kkDisplayVertexColorSeparately フォルダ
の2つがあります。
kkDisplayVertexColorSeparatelyフォルダの方にはPythonのスクリプトとQtDesignerで作ったuiファイルが入っています。
これらをMayaのSCRIPT_PATHやPYTHON_PATHが通った場所にコピーして下さい。
※よく分からない方は C:\Users\ユーザー名\Documents\maya\2017\scripts
日本語版は C:\Users\ユーザー名\Documents\maya\2017\ja_JP\scripts などにコピーして下さい。
melファイルの方はなくてもいいんですが、実行を簡単にするために用意しました。
このmelに処理が書いてあるpythonファイルの実行コードが書いてあるので、
melで実行される場合は
kkDisplayVertexColorSeparately;
Pythonで実行される場合は
from kkDisplayVertexColorSeparately import kkDisplayVertexColorSeparately
kkDisplayVertexColorSeparately.main()
と入力して下さい。
次は使い方です。
実行すると、上のGIF動画にあるようなウィンドウが表示されます。
確認・編集したいチャンネルのボタンを押してもらうと、変更されます。
RGB(A)別々にペイントする場合はペイントツールのカラー値を白黒で変更して下さい。
どれもOFFの状態でペイントするとRGB(A)にそのまま反映します。(この場合のカラー値は白黒じゃなくて良いです。)
ちなみにコンポーネントエディタでの編集も大丈夫ですが、そちらから変更する場合は
チャンネルのボタンを押している状態でRGBをすべて同じ数値を入力されるか、Rにのみ数値を入力して下さい。
(それぞれのtempカラーセットのvertexColorR値を取得して反映させているためです。)
ウィンドウの下の真ん中にある丸いボタンはMaya標準の頂点カラーペイントツールに切り替えるボタンです。
その左側にあるrevertボタンは、このウィンドウを開いたときの頂点カラーの状態に戻すボタンです。
ウィンドウの上の方にターゲットとなっているオブジェクトの名前も表示していますが、
名前が変更されたら一応そこも表示も切り替わります。
はい、使い方は以上です。
というか、複雑な機能はついてないので使い方もシンプルですがw
ここからはこれを作成する上でハマったポイントなどを書いておこうと思います。
①Python API2.0 で行う頂点カラーのセット
maya.cmdsで頂点カラーをセットする場合はpolyColorPerVertexコマンドで行います。
でも、少しでも処理を早くするためにAPI2.0を使った方がいいかなと思い、
調べた所MFnMeshクラスにsetVertexColorというメソッドがありました。
なので、
for x in xrange(len(vtxArray)):
self.targetObjMesh.setVertexColor(om2.MColor([ vtxColors_R[x].r, vtxColors_G[x].g, vtxColors_B[x].b ]), x)
最初こんな感じで頂点カラーをセットしようとしていました。
しかし!!!
これが遅いすぎる・・・
約4万頂点のもので試して時間を計測した結果、塗る度に10数秒待たされます・・・
各頂点ごとにsetVertexColorをやっているのでそりゃ遅いですよね。
どうすればいいんだろうと、色々調べていたら、setVertexColorsというメソッドを見逃していました。
こちらは引数にMColorArrayとVertex Index Listを必要とし、メッシュに対して1回でまとめて頂点カラーを変更するものです。
なのでそれを使って、最終的にはこんな感じ↓で頂点カラーを変更しています。
targetObjMeshからgetVertexColors(カラーセット名)でMColorArrayを取得し、
targetOnjMeshからnumVerticesで頂点数を取得して、xrange(vtxCount)でVertex Index Listを作り、
それらをsetVertexColorsで使っています。
これによって、10数秒かかっていたのが0.18秒くらいなりました。
あと、下記のコード内(部分抜粋)に出てきたのでついでに小さなハマリポイントを書いておきます。
まぁPython中級者以上には当たり前かもしれませんが、Pythonでリストをコピーする場合の注意点です。
baseVtxColors = self.targetObjMesh.getVertexColors(self.baseColorSet)
という部分があり、ここで取得したベースの頂点カラーのMColorArrayをベースとして
RGBAそれぞれ用の一時的なMColorArrayを作りたいと思い、
baseVtxColors_R = baseVtxColors
baseVtxColors_G = baseVtxColors
baseVtxColors_B = baseVtxColors
と書いてました。
しかし、やってみると挙動がおかしい・・・
無駄になるけど、試しにこれはどうかなと思い、
baseVtxColors_R = self.targetObjMesh.getVertexColors(self.baseColorSet)
baseVtxColors_G = self.targetObjMesh.getVertexColors(self.baseColorSet)
baseVtxColors_B = self.targetObjMesh.getVertexColors(self.baseColorSet)
とすると、挙動は正しいけど、同じデータを何度も取得するのはおかしいだろ・・・
調べているとこんな記事を見つけました。
python - リストを複製またはコピーする方法は? - list - copy | CODE Q&A [日本語]
よく今まで引っかからなかったなと思いましたが、
listA = listB
と書いてもリストがコピーされるのではなく、リストへの参照がコピーされ、同じリストを参照するだけなのです!!
それを回避する方法は色々あるようですが、今回は
listA = listB[:]
という書き方で回避しました。
自分と同じようなPython初心者の方はご注意下さい。
②頂点カラーの変更によって実行されるscriptJob
これも今回なかなかハマったポイントです。
scriptJobのattributeChangeでアトリビュートを指定するのですが、
プリミティブのSphereなどで色々試していると メッシュ名+.colorSet で反応していることが分かりました!
しかし、バインドしてあるようなモデルで行うと同じアトリビュートでは反応してくれません・・・
なぜだ!!とさらに調べを進めていると、
そもそもバインドされているようなモデルの場合、
頂点カラーを編集するたびにカラーセット分のpolyColorPerVertexノードができていることに気づきました。
つまり、デフォームされてたりバインドされたりして中間オブジェクトをもつモデルは
編集された頂点カラー情報をメッシュではなく、polyColorPerVertexノードに格納されているということですね。
そこで、下のような感じ(部分抜粋)でスクリプトからカラーセットを変更しつつ頂点カラーを設定し、
polyColorPerVertexノードが繋がった後に、シーン内のpolyColorPerVertexノードを取得し、for文でリストを回しつつ
colorSetName = mc.getAttr("%s.colorSetName"%polyColorVertexNode)
でカラーセット名を取得して、その名前から判断して
分かりやすいようにpolyColorPerVertexノードをrenameしています。
そして、最終的にバインドされているモデルの場合はscriptJobでアトリビュートを指定するときに
ユニークな名前にrenameしたそれぞれの polyColorPerVertexノード+.vertexColor と指定しています。
ちなみに、API2.0使うついでにscriptJobのattributeChangeも
MNodeMessageクラスのaddAttributeChangedCallbackを試してみようかな~と思い使ってみましたが、
返ってくるコールバック内容が今回の頂点カラーの変更にはなかなか使いづらそうだったので諦めました。
ただせっかくなので、1つ使ってみようかなと思い使っているのがaddNameChangedCallbackです。
※こちらこそscriptJobのnodeNameChangedで十分ではあるのですがw
self.callbackID_nameChanged = om2.MNodeMessage.addNameChangedCallback(mObj, self.targetObjNameChangedCallback)
こんな感じで設定しておいて、下のような感じ(部分抜粋)にやっています。
今回試しのprint文でしか使ってないのですが、scriptJobとは違って
addAttributeChangedCallbackだと変更前の名前も同時に取得できるのでそれを使った処理もできそうですね。
また、MNodeMessage.addNameChangedCallbackを調べていて、それにTransformノードを渡すと、
色々なプラグ(アトリビュート)が返ってくるので、Transformノードの変化によってに何かやりたい場合、
scriptJobを複数登録しておくよりも、addNameChangedCallbackを登録しておいて、例えば1つの関数の中で
if "translateX" in plug.name():
~~~
elif "rotateY" in plug.name():
~~~
というような書き方もできるなぁ~という知見が得られました。
③最近ちょっと流行っているフレームレスのPySideウィンドウ
PySideを使ったウィンドウはいくつか作っていましたが、そんなに凝ったものはまだ作ったことはなかったのと
最近のおしゃれウィンドウブームに少しだけ乗っかって、途中からウィンドウを作り直しましたw
参考にしたのは、今回声を掛けてもらった@unpyside さんのブログです。
【MayaPySide】ちょっとおしゃれなUIメソッド【1日目】 | unpyside
今回のMaya Python Advent Calendar 2017で計3回もPySideのことを書いて下さってますね。
大変勉強になります。
個人的には@unpyside さんが最近のPySideによるおしゃれなUIの火付け役なのかなと思ってたりします。
ここでのハマったポイントは@unpyside さんの記事を参考にしたので、すぐに解決できましたが、
ウィンドウの角を丸くするというのはStyleSheetでやるのかと思ったら
QPainterPathとQRegionとsetMaskを使ってマスクをかけるんですね!
なるほど~と思いました。
あとは、公開直前までウィンドウ下の真ん中のペイントツールボタンはなかったのですが、
自分で何度も試していたときにメニューからツール変更するのが不便だったので急遽付け足しました。
そこでアイコンどうしようかなと思った時にこちらの記事を参考にさせて頂きました。
Mayaにある画像をPySideで使う! | KIWAMIDEN
ペイントツールアイコンの取得方法が分かったので無事ボタンを付け足せました。
いかがでしたでしょうか?
あまりゆっくり書く時間がなくて、ざっくりとした解説になってしまいましたが、
もしなぜこうやっているのか意味が分からない部分や、処理的にこうした方が早くなる or 不具合でなくて安全など
ご意見がありましたら、ここのコメント欄やTwitterの方にご連絡下さい。
コメントをお書きください