2007年05月04日

ラティス変形のプログラムを作ってみよう その6 modo203

さて、だいぶプログラムの説明も進んで来たので、そろそろラティスのプログラムを公開することにしたよ。解説を書いててバグを発見したり、仕様を変更したりなんてことは起こるので、あんまり早く公開しても訂正が大変になるからね。

今回はモーフマップの取得方法からだ。modoでは各ポイントに基本的な形状情報以外に付加情報として、UVマップ、モーフマップ、ウェイトマップなどの情報が付加出来るようになっている。例えば座標値(30,50,−100)の点はUVマップ上では(0.5,0.2)の位置だとか、ウェイト値が80%だといった具合に1つのポイントに対してどんどんいろんなデータを付加していける。この頂点(バーテックス)に対してデータがマッピングされる仕組みのことをバーテックスマップと言う訳だね。ウェイトマップは値として1つだけど、UV値ならU値とV値の2つでセット、モーフマップならXYZの3値といった具合にマッピングできるデータのサイズや個数は自由度がある。こういう柔軟なものって言うのは取り扱いに注意しないと、思わぬバグを産む事にもなりかねないところがある。自分もつい先日ミラーモーフでとんだ失態をしでかしたのは記憶に新しいところだ・・・orz 

モーフマップに限らずヴァーテックスマップについては全部同じクエリーで取得出来る。

query layerservice vert.vmapValue ? ポイントインデックス

これだけ。これを見て、このクエリーが何のバーテックスマップを取得しているかわかる?もちろん分かるはず無いよね。だって指定していないんだから。このクエリーも実行前に他のクエリーによって対象となるVMAP(ヴァーテックスマップを以下こう書くね)を設定しておかないと、ちゃんと動作しないクエリーだ。マニュアルにも「頂点マップはvmap.???アトリビュートのうちの一つを使用して、あらかじめ”選択”しておく必要があります」って書いてある。ついでにこのポイントがどのレイヤー内のものかも指定しておかないとダメだな。

VMAPもレイヤーと同じようにインデックス番号で管理されていて、クエリーで指定するときにはその番号を使う。今回のスクリプトでは現在選択されているモーフマップを対象に相対モーフの値をクエリーするので、上記vert.vmapValueのクエリーの前に現在選択されているモーフマップをクエリーして選択しなくちゃならない。そこでマニュアルを読んで現在選択されているVMAPをクエリーする方法が無いか探したけど、select.vertexMapもダメで、唯一使えそうなのは、

query layerservice vmap.selected ? VMAPインデックス

だけだった。このクエリーは指定のVMAPが選択されているかどうかを調べるクエリーで選択されていれば真(True)、選択されていなければ偽(False)を返すものだ。これを使うとなると、登録されている全てのVMAPを順に調べて行くしかないわけだ。そこで登録されているVMAPにどんなものがあるかを調べるクエリーが必要になる。マニュアルを調べたらこんなのが見つかった。

query layerservice vmaps ? 調べるVMAPの種類

調べるVMAPの種類としては、

  • All: 全ての頂点マップ
  • Selected: 選択されている頂点マップ
  • Weight: 全てのウェイトマップ
  • Texture: 全てのテクスチャマップ
  • Subweight: 全てのサブディビジョンサーフェイスウェイトマップ
  • Morph: 全てのモーフマップ
  • AbsMorph: 全ての絶対モーフマップ
  • RGB: 全てのRGB カラーマップ
  • RGBA: 全てのRGBA カラーマップ

がある。今回はモーフマップなのでMorphで絞り込めばよく、

query layerservice vmaps ? Morph

としてやれば登録されているモーフマップのインデックス番号が取得出来る。これを1つずつ調べて行けば、選択されているマップが見つかるわけだ。以下がそのプログラム。

mmaps=lx.evalN("query layerservice vmaps ? morph")
for v in mmaps :
    if lx.eval("query layerservice vmap.selected ? %(v)s" % vars()) :break

if文の条件式としてvmap.selectedのクエリーが使われている。この条件が満たされた時、breakが実行される。このbreakは何者かと言うと、for文などの繰り返しを途中でやめちゃうための仕組みだ。だからこのプログラムは現在登録されているモーフマップをインデックス番号の若い順から調べて行って、選択されているものが見つかったらfor文の繰り返しを中断する動作をする。中断してこれ以上vmapに対するクエリーが行われなければ、そのVMAPが今後のクエリーの対象になるので、この後実行されるvert.vmapValue のクエリーは現在選択されているVMAPに対して行われることになる。親切にプログラムを書くなら、VMAPが1つも選択されていない事も考える必要があるけど、そのチェックは今回は割愛した。やりたいなら、変数をひとつ作ってFalseで初期化して、見つかった時点でTrueを代入してからbreakするようにして、for文の出口でその変数をチェックすればいいだろう。

これでモーフマップを取得する準備が出来たので、アイテムに登録されている全ポイントのリストを取得して、そのポイントインデックスを使ってvert.vmapValueのクエリーをしたのが以下のプログラムだ。

mpos=[ ]
verts=[int(v) for v in lx.eval("query layerservice verts ? all")]
for v in verts:
    vmap=lx.eval("query layerservice vert.vmapValue ? %s" % v)
    if vmap==None:
        mpos.append([0.0,0.0,0.0])
    else:
        mpos.append(vmap)

最初に変数mposに空のリストをセットして、これにモーフマップ値を追加していく。リストに新しい要素を追加するにはappend()メソッドを使う。

リスト.append(値)

でリストの一番後ろに値を追加出来る。VMAPは全てのポイントにくっついているとは限らない。該当のモーフマップが設定されていないポイントをクエリーするとNoneが返って来る。後の処理の事を考えてNoneが返って来たら、[0,0,0]としてリストに登録しておくことにした。
このプログラムを抜けると変数mposにラティスの全てのポイントのモーフマップの値がリストになって、変数vertsに登録されているポイントインデックスの順に入っていることになる。

これでラティス変形のための部品はほぼ出揃った。ここでもう一度プログラムの流れをまとめておくと、

  1. ラティスとその親の変形されるメッシュのアイテムIDを取得する
  2. ラティスアイテムの移動、回転、縮尺のパラメータを取得する
  3. 2で得た値から座標変換行列を生成する
  4. ラティスの構造を分析して大きさや分割数、間隔、各ポイントの座標値、モーフマップの変位量などを取得する
  5. 変形されるメッシュのポイントを全て取得する
  6. 変形されるメッシュの各ポイントをラティスの入ったアイテムの座標系に変換して、そのポイントがラティス内のどの仕切りの中に存在するかを調べる
  7. 5で得られたポイントを囲む8つのラティスのポイントと、メッシュポイントの関係を調べて8つのポイント座標でそのメッシュポイントを表現するためのパラメータを得る
  8. 5で得られた8つのポイントを4で得られたラティスポイントの座標値とモーフマップの変位量から変形されたラティスの座標値を算出し、3で生成した行列でその座標をメッシュ座標系に変換し、その結果と6で得たパラメータとを使ってメッシュポイントの移動位置を算出する
  9. 7で得られた座標値にメッシュポイントを移動する

となるわけだ。

アップしておいたプログラムにはサンプルファイルも付けて置いたよ。今のところラティス自身の生成プログラムは作っていないわけで、とりあえずこれで試してみてくれ。その際注意しなくちゃならないのは、スクリプト起動時のアイテムとモーフマップの選択状態だ。詳しくはその1の時に載せておいたGIFアニメを見てもらうとして、ラティスとメッシュに同じモーフマップを設定して、ラティスにモーフ変形を加えてから、そのモーフマップを選択したまま、アイテムもラティスを選択したままスクリプトを起動すれば親アイテムのメッシュが変形されるはずだ。最終的にはパネルを作ってちゃんと操作出来るようにしたいね。

それではまた次回。

スクリプトまとめページ( Down Load はこちらから)  

カテゴリー別ページ

modo操作メモ



take_z_ultima at 11:34│Comments(8)TrackBack(0)modo | CG

トラックバックURL

この記事へのコメント

1. Posted by mot   2007年08月30日 17:44
はじめまして、motと申します。
こちらのブログにを参考書代わりに、pythonの勉強を始めました、
プログラム初心者です。
ひとつ質問させてください。
今回の記事でわからない所があります。
if lx.eval("query layerservice vmap.selected ? %(v)s" % vars()) :break
この式のvars()は何でしょうか?

自分では
if lx.eval("query layerservice vmap.selected ? %s" % v) :break
だと思い、変えても動作は変わらなかったので、余計に解らなくなってしまいました。

ご面倒かもしれませんが、よろしくお願いします。
2. Posted by take_z   2007年08月30日 18:37
motさんこんにちは。自分も最初にこの式に出くわした時は「ナンじゃこりゃ」と思いましたよ。このvars()は今の階層の変数の辞書を提供してくれるファンクションで、文字列の中に書かれた書式"%(変数名)s"の変数名をキーにして辞書からキーに対する値を取り出して文字列に変換してくれます。
最大のメリットは、文字列の中に変数名を埋め込んで行けるので、%sと後ろに書いた変数との対応を間違わなくて済むし、読み易いってところにあります。
ためしにPython環境で、a=10と実行して、次にvars()を実行してみれば、表示される辞書の中に{'a':10}の項目が見つかると思いますよ。書式変換の%(名前)sはその辞書の中から対応する値を引っぱってきて文字列に変換する機能があり、両者を組み合わせたのがその文という事です。
だから別にどう書いてもいいと思いますよ。
3. Posted by mot   2007年08月31日 23:17
take_zさん、こんばんは。お返事が遅れましてスミマセン。
仕事の合間をぬっての勉強な上、昨日返信していただいた内容について読んでも初めはピンと来ず、ようやく先ほどナルホドとなった次第です。
確かに、こう書く方がスマートになりますね。持ってた参考書にも書いていなかったので目から鱗です。
また、きっと(イヤ絶対)質問することがあるとは思いますが、そのときはよろしくお願いいたします。
ありがとうございました。
4. Posted by take_z   2007年08月31日 23:41
motさん、お仕事お疲れ様でした。
Pythonについては自分もこのブログをつけながら勉強し始めたので、誤った記述もいろいろあると思います。たいして詳しくは無いですが、自分が踏んだ轍くらいはお伝えできると思います。
5. Posted by mot   2007年09月04日 17:44
take_zさん、こんにちは。
またまたすみません。質問です。
あれから試行錯誤しながら自前のスクリプトを書いているのですが、ダラダラと書いていくうちに、ふとプログラムの頭の方にエラー処理を入れたくなりまして、条件分岐(if)で書いたのですが、入れ子にする下の文が長い場合に結構めんどうだなと思いました。
そこで、プログラム自体を好きな所で終了するコマンド(もしくは方法)って有りますか?
もちろんエラー部分で勝手に止まるのですが、せっかくエラー処理のメッセージを出しているのでメッセージダイアログ後に処理が止まる方がきれいだなと。(完全に自己満足の世界ですが)
もちろん、無ければ地味にインデントを足していきます。
特に急いでおりませんので、お時間のある時によろしくお願いします。
6. Posted by take_z   2007年09月04日 19:14
これも自分も困っている事のひとつで、通常のPythonのプログラムならsysモジュールをimportして、sys.exit(0)みたいにすれば出来るんですが、スクリプトの場合はOSに終了を頼む訳にもいかないんですよねぇ。perlにはdieが用意されてるんですが、残念ながら自分の調べた範囲ではPythonにはそういうのが無いみたいです。
今のところ自分はインデントしてます。お手軽に止めたいなら例外を発生させちゃうのも手だと思います。

raise Exception, "エラーメッセージ"

みたいな感じで。このraiseを通ればmodoがエラーダイアログを出してスクリプトは止まってくれます。
lxモジュールにスクリプトを止めるlx.exit()とか付けて欲しいですね。

みんな同じところで困っているわけですね。この辺のことはそのうちまとめてどこかに書いておいた方がいいですね。
7. Posted by mot   2007年09月04日 23:51
やっぱり無いですか〜。
まぁ、トリッキーな記述より後で見てインデントの方がわかり易いでしょうから(特に自分みたいな初心者)、インデント追加をエデェタのマクロにでも登録して、少しでも労力を減らします。
ともあれ、初めての自前のスクリプトは完成しました。take_zさんのブログのおかげです。(なにせ半分以上コピペですから)ありがとうございました。感謝感謝です。
8. Posted by take_z   2007年09月05日 02:52
無いと断言できるほど詳しくないので、その辺はご了承下さい。
プログラムが完成して良かったですね。自分がアップしているスクリプトはファイル名の通りサンプルですから好きに流用して下さい。相当間抜けなコードもいっぱい混ざってますから、お気をつけて。

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 

Archives