前回までPythonの大まかな説明と学習方法などを説明してきました。
小難しいことが嫌いな人向けのMaya/Python入門①~Python編~
小難しいことが嫌いな人向けのMaya/Python入門②~準備編~
大まかな概念がわかったところで早速例題としてツールを作っていきます。
「え、いきなり!?」って思うかもしれませんが、Pythonについて知り尽くしてからってなるとモチベーションが続かないし、いざ使うときになって忘れてしまうので、分からないことがあったときにそれについて調べた方が記憶に定着すると思うからです。
というわけで今回作成するのは「ウェイト書き出し、読み込み」ツールです。
Mayaの標準ツールにウェイトマップで書きだす方法がありますが、UVをきれいに開かないといけないし、解像度によって精度と容量が左右されるのでデータとして使い勝手が悪い。
ポリゴンメッシュの頂点には必ず番号が割り振られているので、その頂点番号とウェイト値の組み合わせをテキスト形式でファイル出力すれば上記のような問題が解決できるはずです。
必要そうな機能・処理
ツールのイメージとしては、ツールには「書き出し」ボタンと「読み込み」ボンタンがあり、ウェイトを書き出すメッシュを選択した状態で「書き出し」ボタンを押すとダイアログが開き保存先を指定しデータに書き出される。また、同じメッシュを選択した状態で「読み込み」ボタンを押すとダイアログが開き読み込みファイルを指定するとウェイトが読み込まれる。
という感じです。
上記の内容から必要そうな機能・処理をリストアップします。
- 選択状態のメッシュに処理を行う。
- 各頂点が持っているウェイト情報の取得(書き出し)。
- ウェイト情報をメッシュに再登録(読み込み)。
- ウェイト情報をJSON形式のファイルとして入出力。
- ウィンドウを作る。
これらの処理を一つ一つ作成していけばツールが出来ます。
処理の実装
#-*- encoding: utf-8
import maya.cmds as cmds
import json
def getSkinClusterName(mesh):
history = cmds.listHistory(mesh)
for i in history:
if cmds.objectType(i,isType='skinCluster'):
skinClusterName = i
return skinClusterName
def getWeight(mesh):
skinClusterName = getSkinClusterName(mesh)
numberOfVTX = cmds.polyEvaluate(mesh, v=True)
weights = {}
weights[mesh] = {}
for i in range(numberOfVTX):
weights[mesh][i] = {}
influenceList = cmds.skinPercent(skinClusterName,mesh+".vtx["+str(i)+"]", t = None, q = True)
for j in influenceList:
weight = cmds.skinPercent(skinClusterName,mesh+".vtx["+str(i)+"]", t = j, q = True)
if weight != 0:
weights[mesh][i][j] = weight
return weights
def putWeight(mesh,weights):
skinClusterName = getSkinClusterName(mesh)
numberOfVTX = cmds.polyEvaluate(mesh, v=True)
for i in range(numberOfVTX):
VTXWeight = []
for j in weights[mesh][str(i)]:
VTXWeight.append([j,weights[mesh][str(i)][j]])
cmds.skinPercent(skinClusterName, mesh+".vtx["+str(i)+"]", tv = VTXWeight)
def exportFile():
filePath = cmds.fileDialog2(fileFilter = '*.json', cap = '書き出し', okc = '書き出し',ds = 2, fm = 0)
if filePath == None:
return False
mesh = cmds.ls(sl=True)
WeightDate = {}
for i in mesh:
WeightDate.update(getWeight(i))
with open(filePath[0], mode = 'w') as fileWrite:
json.dump(WeightDate,fileWrite, indent = 2)
def importFile():
filePath = cmds.fileDialog2(fileFilter = '*.json', cap = '読み込み', okc = '読み込み',ds = 2, fm = 1)
if filePath == None:
return False
with open(filePath[0],mode = 'r') as fileRead:
weight = json.load(fileRead)
mesh = cmds.ls(sl=True)
for i in mesh:
putWeight(i,weight)
testWin = cmds.window(title='ウェイトの書き出し', width=280)
cmds.columnLayout(adjustableColumn=True)
cmds.button(label='書き出し',command='exportFile()')
cmds.button(label='読み込み',command='importFile()')
cmds.setParent('..')
cmds.showWindow(testWin)
※↑コード中の隠れてしまっている部分は下のスライダーを移動すれば確認できます。
このコードは実際に作成したものです。一旦これをコピーしてスクリプトエディタで実行してみてください。
ツールの操作方法は以下の通りです。
【書き出し】
- ウェイト付けしたメッシュを選択します。
- 「書き出し」ボタンを押すとファイルの保存場所を決めるダイアログがでるので、保存場所を決めます。
- そのままウェイトデータの書き出しが行われます。
【読み込み】
- ウェイトを書き出したメッシュを選択します(バインドはした状態で)。
- 「読み込み」ボタンを押すと、データの選択のダイアログがでるので、書き出したjson形式のウェイトデータを選択します。
- ウェイトが読み込まれます。
動作確認ができたところで、それぞれの処理がどのように行われているか簡単に解説していきます。
コード解説
記述順の簡単な決まり
まず、2~3行目に書かれているように一番上にはモジュールのインポートを記述します。使いたい機能によっては上記以外にもインポートする必要があります。
6~54行目で関数を記述し、その次に56~61行目でウィンドウ作成の記述をしています。この順番が実は大事。
スクリプトは基本的には上から順に処理が行われます。「def」ではじまる関数の記述は「作成」をしただけで、他の記述から「呼び出し」されるまで処理は行われません。その呼び出しを行っているのがウィンドウ作成処理の記述部分です。「書き出し」ボタンを押すとウェイトデータ書き出しに関する関数の呼び出し処理を行い、「読み込み」ボタンを押すと読み込みに関する関数を呼び出します。
重要なのは、上から順に処理が行われていった時に「関数の作成」前に「関数の呼び出し」を行っても「その関数はありません」と言われてしまうということです。
実際の処理の順番
大まかにですが処理が進んでいく順番を解説します。どのように処理が進んでいくかを知ることで、コードを書くときの感覚がつかめるはずです。
- 「書き出し」ボタンを押す。
- 58行目:button関数の引数「command」に渡されている「exportFile」関数を呼び出す。
- 36行目:「fileDialog2」関数によりファイルパスを指定するダイアログ表示とその結果を「filePath」へ代入
- 37~38行目:ファイルが指定されなかった場合に処理を終了するif文
- 39行目:ls関数により現在選択しているメッシュの名前を取得し、「mesh」へ代入
- 40行目:ウェイト値を入れるための辞書型変数を宣言
- 41~42行目:選択したメッシュの名前を順番にgetWeight(ウェイト値を取得する関数)へ渡し、帰ってきた値を変数WeightDateへ追加していく
- 13行目:getSkinClusterName(ウェイト値を格納しているスキンクラスターの名前を取得する関数)にメッシュ名を渡し、帰ってきたスキンクラスター名をskinClusterNameへ代入
- 6行目:メッシュが持っている様々な情報ノードを全て取得し、変数historyへ代入
- 7~9行目:変数historyに格納されているノード名の中からskinClusterの名前を探し、変数skinClusterNameへ代入
- 10行目:変数skinClusterNameを呼び出し元に返して、getSkinClusterName関数の処理を終了
- 14行目:polyEvaluate関数でメッシュの頂点数を取得し変数numberOfVTXへ代入
- 15~16行目:空の辞書型変数weightsを宣言し、メッシュ名のキーを作成
- 17~23行目:メッシュの各頂点の各インフルエンスのウェイト値を取得し、ウェイト値が付いているもののみ変数weightsへ代入
- 17行目:numberOfVTXの頂点数を基に0~頂点数までの数字を順に変数iに代入して23行目までをリピート
- 18行目:辞書型変数weightsのメッシュ名キーの中に頂点インデックス数のキーを作成
- 19行目:skinPercent関数の引数に渡した頂点が保有しているインフルエンス名を全て取得し、リスト型変数influenceListへ代入
- 20行目:変数influenceListに格納されているインフルエンス名を順に変数jへ代入し23行目までをリピート
- 21行目:変数jで渡されたインフルエンス名のウェイト値を取得し、変数weightへ代入
- 22~23行目:ウェイト値が0じゃないならそのウェイト値を変数weightsのメッシュ名キー→インデックス名キー→インフルエンス名キーへウェイト値を格納
- 変数weightsを呼び出し元に返して関数getWeightの処理を終了
- 43行目:36行目で取得した保存先ファイルパスを書き込みモードで開きその情報をfileWriteへ代入。44行目までの処理が終わったら保存先ファイルパスを閉じる
- 44行目:変数WeightDateの内容をそのままjson形式のファイルに書き出す。
コードで使われているMaya Python関数
- listHistory() :引数に渡したオブジェクトに繋がっているノード名を全て返す。今回の場合はskinClusterノードを知るために使用した。
- objectType():引数に渡したオブジェクトのタイプを調べる。今回は渡されたノード名がskinClusterであるかどうかを調べるために使用した。
- polyEvaluate():引数に渡したメッシュオブジェクトの頂点・エッジ・面などの数を返す。コンポーネントに対してforループするときなどによく使う。
- skinPercent():各頂点の保有インフルエンス名やそのウェイト値を得たり、ウェイト値を設定することができる。
- fileDialog2():ファイルの読み書きダイアログ表示と指定したファイルパスを返す。
- window():ウィンドウ作成するための値を返す。
- columnLayout():子を縦一列に配置したレイアウトを作成します。
- button():ボタンを作成する。
- setParent():既定の親を、指定した親に変更します。
- showWindow():ウィンドウを表示する。
まとめ
「書き出し」ボタンを押したときの処理について大まかに説明しましたが、これだけでは詳細な処理内容についてはわからないと思います。Pythonの文法やMayaの関数については準備編で解説した通り調べることができます。そういったドキュメントを確認しながら上記のコードや解説を読むことでより理解が深まるはずです。
「読み込み」ボタンを押したときの処理内容は「書き出し」ボタンの処理と似ているので、応用として処理内容を追ってみると良いでしょう。
おまけ
以下の「現場で使えるMayaスクリプティング」は実際に使えるコード集ですが、「maya.cmds」ライブラリをインポートして行うスクリプッティング(今回説明したような)ではなく、「PyMEL」のライブラリを使用したスクリプティングです。
本書ではPyMELの方がわかりやすく学習に向いているとのことで解説していますが、個人的にはその中でもMELと並行したPython基礎の解説(本書の3分の1ほど)がすごく参考になりました。