8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HoudiniAdvent Calendar 2024

Day 3

へ~、Pythonの中からSOPが呼べるんですね~

Last updated at Posted at 2024-12-02

殺人的な猛暑日の続く夏からいきなり冬になった感のある今日此の頃ですが、皆様如何お過ごしでしょうか?
Houdini触ってますか?

今日はPythonSOPについてお話したいと思います。

wrangle超速い、Pythonめっちゃ遅い!
wrangle超短く書ける、Python超ゲロクソ長いコードになっちゃう~!

そうお考えの方も多いと思います。
Pythonさん、実はいろいろ出来るんですよ。
器用な人なんです。


ある日ふとPythonSOPの
image.png
Pythonコードの右上の▼を押して眺めてたら
image.png
あれ?下の2ついつの間にか増えてる???
なんだコレ?
Verbってことは挙動?SOPとして動いてくれるってこと?
ApplySubdivide SOP Verb にして上にBOXを繋いでみたら
image.png
SubdivideSOPが無いのにPythonの中でSubdivideやってくれてる~!

じゃぁテキストのリスト読んでFileSOPで配置とか軽く出来そうだし
いろいろ生成したものを条件付けて吐き出すのも簡単そう!
やるぅ~!

このスニペットでは

verb = hou.sopNodeTypeCategory().nodeVerb("subdivide")

verb.setParms( { 'iterations' : 1 } )

geo.execute(verb)

SOPの指定、パラメータの指定、実行という流れで行っています。
大変わかりやすいですね。

分かりづらいのはここ

node.geometry().clear()
node.geometry().merge(geo)

ここを

geo.clear()
geo.merge(geo)

こう書き換えても動作しないんですよ。
動作しないというのはBOXの結果のまま変化が無いということです。

ちょっと紛らわしいんですが
geoの中にはSOPの計算結果の形状が入っています。
しかしまだ実際の形状としては反映されません。

node.geometry().clear()

この行で一旦ジオメトリを消して

node.geometry().merge(geo)

計算結果を実形状にマージして完成という流れになります。

geoはあくまでメモリ上のインスタンスということなんでしょうか。

私が紛らわしいと思った理由はあります。
別の上から二番目のPythonスニペットでこう書かれています。

# Move Points Up
node = hou.pwd()
geo = node.geometry()

# Add code to modify contents of geo.
for point in geo.points():
    pos = point.position()
    pos += hou.Vector3(0, 1, 0)
    point.setPosition(pos)

ぱっと読んですぐに分かると思いますがこれは全頂点をY+方向に1だけ動かす簡単な例です。
これは実際の形状の頂点を直接動かしています。

それに対して

geo.execute(verb)

は実際の形状にすぐには影響を与えません。

なので紛らわしいなと思った次第です。
簡単なスニペットで出来ることだけでなく扱い方の違いまで
考えが及ぶ様に用意されているのは有り難いことですね。

geoというインスタンスは

  • 現在の形状を変更(点の移動)出来る、実形状とリンクしている状態

  • geo.execute(verb)の結果は形状を変化させずに
    メモリの中にだけSOPの計算結果を保持しておくにとどまり
    SOPノード自体は存在せず実行だけを行う

ということがスニペットを通じて理解出来ました。


では軽く実践してみます。

3dnchuさんのこの記事で紹介されているモデルで適当にうさぎなり龍なりを落としてきて

それをcsvファイルにリストしておいて

PythonSOP内で読み込みとID付けとパック化を行い

点群に割り当ててみることとします。

image.png

まず名前とパスが書かれたcsvを用意します。
そしてこれらをHoudiniに読み込むためにPythonSOPを一個作ってその中に下記のコードを書きます。

import csv

models = {}
with open('d:/models/test/models.csv') as f:
    rd = csv.reader(f)
    for i in rd:
        models[i[0]] = i[1]
node = hou.pwd()

verb1 = hou.sopNodeTypeCategory().nodeVerb("file")
verb2 = hou.sopNodeTypeCategory().nodeVerb("xform")

for k,v in models.items():
    geo = node.geometry()
    # ファイル名指定、パック化、見た目フルジオメトリ
    verb1.setParms( { 'file' : v, 'loadtype' : 6 ,'viewportlod' : 0 } )
    geo.execute(verb1)

    bbox = geo.boundingBox().sizevec() # 大きさ取得
    minp = geo.boundingBox().minvec() # 位置調整
    # 接地させて大きさ調整
    verb2.setParms( {'xOrd':5, 'scale' : 1/min(bbox) , 't' : (0,-1*minp[1],0) } )
    geo.execute(verb2)
    
    geo.addAttrib(hou.attribType.Point,'name',k)
    pnt = geo.points()[0]
    pnt.setAttribValue('name',k) # 名前を付けておく
    node.geometry().merge(geo)

このコードを書いたPythonSOPにimport_assetsなどの名前を付けて名前アトリビュートの付いた点群に
CopyToPointsをするとこの様な結果になります。

image.png

1つ目のverbでFileSOPを指定します。
setParmsで使用されているパラメータ名の調べ方ですが

    verb.setParms( { 'file' : v, 'loadtype' : 6 ,'viewportlod' : 0 } )

fileの項目は言わずもがな2つ目はFileSOPを実際に出して
Loadのパラメータの上にカーソルを置くとバルーンが出てパラメータの詳細が出ます。
image.png
Loadの下にParameter : loadtypeとありますのでそれがパラメータ名です。
値が6となっているのは
image.png
Loadのプルダウンメニュー、Houdini風に言うとオーダードメニューの一番下の
PackedGeometryが上から数えて7番目で一番上を0として数えるので6になります。

3つ目のviewportlodはLoadをPacked系にした時にだけ現れるDisplay asのパラメータです。
パックした際の見た目を決定します。
image.png
これは
image.png
一番上のFullGeometryで良いので0でOKです。

    bbox = geo.boundingBox().sizevec() # 大きさ調整
    minp = geo.boundingBox().minvec() # 位置調整

boudingBox系のクラスのメソッドを使って情報を取っています。
この情報は次のTransformSOPで使います。
boudingBoxクラスのメソッドの詳細についてはこちらを御覧ください。
https://www.sidefx.com/docs/houdini/hom/hou/BoundingBox.html

次に読み込んだモデルの位置やサイズ調整をします。

    verb2 = hou.sopNodeTypeCategory().nodeVerb("xform")
    verb2.setParms( {'xOrd':5, 'scale' : 1/min(bbox) , 't' : (0,-1*minp[1],0) } )
    geo.execute(verb2)

おや?transformじゃなくxformになっているぞ…?
TransformSOPの名前は実はtransformじゃありません。
image.png
何を馬鹿なことを言ってるんだTransformSOPの名前はtransformに決まってるじゃないか、
などと思われることでしょう。
実は違うんです。
image.png
メインメニューからAssets -> Asset Definition Toolbar -> Show Menu Always を設定して下さい。
すると・・・
image.png
Asset Name のところに xform と書かれているのが分かります。

罠ですね。

引っかからない様にしましょう、
こんな罠にいちいち引っかかっている様では生き残れませんよ。
image.png
この辺も生存バイアス風味ではありますがフロムゲーよりはマシなので泣き言はほどほどにしときましょう。
俺達はフィーリングでHoudiniを触っている。

   verb2.setParms( {'xOrd':5, 'scale' : 1/min(bbox) , 't' : (0,-1*minp[1],0) } )

xOrdが5というのはTrans Rot Scaleの順で計算するということです。
おおむね一番サイズの小さい軸でスケールを合わせておけば良い感じだったので今回はUniformScaleで
1/min(bbox)
としました。
もし軸ごとにスケールを変えたい場合はsx,sy,szみたいな指定ではなく移動と同じ様に
sでベクターのタプルで指定してあげて下さい。
移動に関してはY座標の一番低いところをXZ平面に合わせる様にしています。

あとは名前のアトリビュートをポイントに設定してCopyToPointに渡すだけですね。

image.png

Copy To Point SOP にはPiece Attributeを設定して下さい。
image.png

Copy To Point SOP の2つ目の入力のPoint Wrangleには下記の様に書いて
それぞれのPointにnameアトリビュートを設定しておくと良い感じにバラバラに配置できます。

string names[] = {"blub","bob","bunny","dragon","happy","spot"};

int rnd = int(rand(@ptnum+chi("seed"))*5.99999);

s@name = names[rnd];

image.png
Scatterの代わりに Scatter And Align を使っても便利です。
回転や大きさなどを簡単に良い感じにバラバラに出来ますし重ならないように
調整しやすいと思います。
image.png


いかがでしたか?
もうFileSOPをモデルの数だけシーンの最初に置くとかやらなくていいんです
そうPythonならね。
何千回何万回と頂点に対して処理する様な処理は弱いですがSOPを扱ったり
外部ファイルとの連携、機械学習との連携などPythonならではのめちゃつよ成分がありますので是非とも使い込んで日々の業務の糧として下さい。
では急に寒くなったんで体調管理などお気を付けてお過ごし下さい。


配布物

hipはここです。(hiplc)

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?