この記事はHoudini Advent Calender 2018の15日目の記事です
今回は、HoudiniのParameter Interfaceで使えるPythonの話を書こうと思います
基本的には自分の経験上で実装する時にちょっと調べた事や普段良く使う構文などのTips が中心で
できること全体をカバーする内容ではありません。
どうやってやるか調べた事あるけど、わからなくて挫折したネタが見つかるかも。という程度でみて下さい。
Menu Script
Menu Scriptを使って inputに応じてMenuの内容を変える方法 を実装してみたいと思います
まず、Menu Scriptは
Orderd Menu Type であれば必ずONになっていますが
String Typeでも use Menuにチェックを入れると使えるようになります
ここに書く内容として必ず守らなければいけないのが
かならず 文字列の配列を返す ということです。
その処理を書き忘れるとWarningが出まくって面倒な事になります・・
厳密には配列には TokenとLabel の2つの要素を 交互にいれる必要があります
TokenとLabelとは、Menu Itemsを直接指定する時に使うこの時の組み合わせのことです
また、もう1つ注意しなければならないのが、返す配列の中身は
[ [token, label], [token, label] ]
では無くて
[ token, label, token, label ]
のようにただ交互に繰り返して並べるだけという事です。
tokenとlabelはペアだと思って直感的に前者の書き方をしそうになりますが違います。
Menu Script Sample
実際にどんな書き方をして要素を作ると簡単かサンプルをあげてみます。
例えば Tokenを数字にしてLabelを入れる場合 は
enumrateとかを併用すると便利です
labels = ["box","sphere","grid"] # 使いたいメニューの項目
result = []
for (i,v) in enumerate(labels): # iに数字が入る(0スタート)
result.extend([i,v])
return result
または TokenとLabelを同じにしたい 場合は
zipとかsumを使って
labels=["P","N","Cd"] #使いたいメニューの項目、attributeとかはtoken同じにする場合が多い
return sum(zip(labels, labels),())
とかやるとまとまります。
以上を踏まえて、冒頭にあったinputに応じてMenuの内容を変えるサンプルを作ってみます
入力ノードのPointAttribute一覧をMenuに表示するサンプル
以下のように書きます。
inputs = hou.pwd().inputs()
result = []
if len(inputs):
node = inputs[0] # inputは複数あるのでとりあえず1番目の例
attrs = [ x.name() for x in node.geometry().pointAttribs()]
result = sum(zip(attrs, attrs),())
return result
かなりよく使う仕組みだと思うので、やってる人も多そうですが紹介してみました。
ちなみに、小ネタですがString TypeのパラメーターをMulti Lineにして
Language を VEXとかPythonにしてMenu Scriptを仕込むと
Wrangleノードでお馴染みの オレオレSnippet が作れます
Action Button Script
続いて ActionButton にスクリプトを書いてみたいと思います
ActionButtonとはMenuの横でよく見かけるこういうやつです
Action Buttonの設定はParameter Descriptionの一番右のタブにあります
とりあえず何か書けばボタンを押した時にそのスクリプトが実行されます
(何も書かれていないとボタンが出ません)
たとえば、ActionButtonを押した時にParameterの値を変化させたい場合は
node = kwargs["node"]
node.parm("変えたいパラメーターの名前").set("変えたい値")
という感じで書けば大丈夫です。
ちなみに、先ほどMenuScriptではノードの取得に **hou.pwd()を使っていましたが
ここでは kwargs["node"] というのを使っています。
どちらも自分のノードオブジェクト( hou.Node )**を返してくれるのですが、どっち使えばいいの?と
おそらく混乱する人も居るのではないかと思います
基本的にHoudiniでPythonを扱う時、ParameterInterface関連でイベントが発生する時は
kwargsに値が格納されてます。イベントによって中身がちょっと違うのですが、何が入っているかはこちらに書かれています
http://www.sidefx.com/ja/docs/houdini/hom/locations.html
もしくは print kwargs と書いておけば何が入ってるかわかりますね。大体、自分のNodeとか押したParmeterの名前とかです。
実は、さっきのMenuScriptもkwargsが使えます
ただ、Menu Scriptは hou.pwd()でもkwargs["node"]でも自分のノードが返ってくるのですが、
Action Buttonは hou.pwd()をしてもルート("/")のノードが返ってきます。
まぁ、なんとなくActionButtonのイベントは評価されている場所が違うのだろうというのがわかると思いますが
誤解を恐れず言ってしまうと
何かのアクションに対して実行するときはkwargs["node"]
常時発動系はhou.pwd()
という感じで覚えておけば大体通ると思います。例外あったらすみません。
hou.pwd → Expressionとか
kwargs → Shelfとかボタンとか
そんなかんじですね
脱線しましたので、話を戻して
####ActionButtonでViewPortから頂点を選択してパラメーターに反映する
というのを作ってみましょう。
Viewportからオブジェクトを選択するには hou.SceneViewer Class
http://www.sidefx.com/docs/houdini/hom/hou/SceneViewer.html
の関数を使えば良いのですが、まずViewportのアクセスにはtoolutilsモジュールが便利なのでそれを使いましょう
とりあえず書くとこんな感じです
import toolutils
node=kwargs["node"]
selection = toolutils.sceneViewer().selectGeometry(geometry_types=(hou.geometryType.Points,))
node.parm("parm").set(selection.mergedSelectionString())
はい、引数が多いので補足します
hou.sceneViewerのselectGeometry()はドキュメントに選択するタイプを指定するにはどうしたらいいのか? が書かれていません。
ただ、良く読んでよく調べるとgeometry_typesという引数にhou.geometryType*の値を与えてあげれば良いことがわかります
hou.geometryType
http://www.sidefx.com/docs/houdini/hom/hou/geometryType.html
ただ.hou.geometryTypeを1つ指定すれば良いのかというとそうではなくて
Tupleで、かつ**(hou.geometryType.Points , )** としなくてはいけないので知らないとハマります。
geometry_types= (hou.geometryType.Points) ではダメです。
また、hou.SceneViewerの selectGeometry()で頂点や面を選択しても
返ってくるデータはhou.GeometrySelection なので
http://www.sidefx.com/docs/houdini/hom/hou/GeometrySelection.html
**mergedSelectionString()**を使うと、空白とか含めて最適な文字列を返してくれます。
pythonに慣れてる人は大丈夫だと思いますが、
引数や戻り値の型が見えづらいので、気をつけながら書く必要がありますね。
ただ、頂点とかオブジェクトを選択してParameterに入れるのも比較的よく使うネタだと思うので紹介してみました。
続いて
HDA で使えるPython の書き方
を見ていこうと思います
これが一番需要がありそうですが、同時に人によってやり方が違いそうな気がします。
あくまで私の個人的なやり方になりますので、参考程度でお願いします。
HDAにScriptを埋め込みには、TypePropertiesのScript欄を使います
まず左側でEvent handlerを定義する必要があります
先ほどのkwargsのリンクと同じページに書いてあるので、順に読んで頂いた方は気づいたかもしれませんが
もう一度リンクを貼っておきます
http://www.sidefx.com/ja/docs/houdini/hom/locations.html#asset_events
おそらくよく使われるのは
OnCreated (HDAを作成した時に実行される)
OnInputChanged (Inputが変わった時に実行される)
PythonModule (汎用モジュール)
あたりだと思います
それぞれに役割を持たせてクラスや関数を書くことが出来ますが
個人的にはPythonModuleにまとめて書くことが多いので
そういった使い方を説明します。
さっそくサンプルとして
####入力したオブジェクトのタイプに応じて色を変える
というHDAを作ってみましょう。
HDA自体の作成方法は省略して、Scriptの登録から説明します。
まず色を変える関数を作っておきましょう
def changeColor(node, rgb):
node.setColor(hou.Color(rgb))
名前は何でも良いですが関数としてPythonModuleに書いておきます
次に入力に応じて切り替えて見ようと思いますが
Polygonなら赤
PackedGeometryなら緑
VDBなら青
にしてみましょうか
*便宜上 1つ目のPrimで判断することとします
OnInputChangedイベントを作り、その中に以下のように書きます
node = kwargs["node"]
inputs = node.inputs()
if inputs:
geo = inputs[0].geometry()
type = geo.prims()[0].type()
if type == hou.primType.Polygon:
node.hdaModule().changeColor(node,(1,0,0))
elif type == hou.primType.PackedGeometry:
node.hdaModule().changeColor(node,(0,1,0))
elif type == hou.primType.VDB:
node.hdaModule().changeColor(node,(0,0,1))
こんな感じのHDAになります(TypeColorというのがサンプルで作ったHDA)
繰り返しになりますが、自分はHDAで使う関数は大体PythonModuleにまとめてしまいます。
この場合、OnInputChangedのイベントからPythonModuleの中にある関数を呼ぶのに
node. hdaModule() というのを使ってますが
このようにPythonModuleにある関数は他のイベントや、イベント外から呼べるのに対して
OnCreateなど特定のイベント内に書いた関数は他から呼び出せない為
結果的にPythonModuleに全て書いたほうが汎用性が高くなるからです
例として、押したら色が黒になる、というボタンを作ってみましょう
ついでに、CallbackScriptというのを説明します
CallbackScriptからPythonModuleを使う
CallbackScriptはParameterDescriptionのところにあります
HscriptとPython両方が使えるので、右端でPythonにしておきます
Pythonはこんな感じに書いてますが
hou.phm().changeColor(kwargs["node"],(0,0,0))
先ほど使った**node.hdaModule()**でPythonModuleを呼び出すのを省略して
**hou.phm()**というのが使えます
こういう良く使う構文の省略形を用意してくれてるのがSideFXの良いところですね
結果的に、ボタンを押した時やインプットを切り替えた時に、それぞれ同じ関数を呼び出して使うことが出来ました。
CallbackScriptは1行で済む処理ならそのまま書いちゃうことも出来ますが
処理を変更したい時に、OnInputChangedやCallBackScriptに書いた内容をそれぞれ編集するのは面倒ですよね
なので、なるべくpythonModuleに書いて、同じ関数を呼び出すようにしています。
どんな処理をさせるかとか、どんな引数にするかとかでも違ってくると思いますが、一応おすすめの方法でした。
で、また小ネタなんですが・・(多すぎですが)
Subnetでよく見るコレに色を付けるとノードのラインの色が変わるのはご存知の方も多いと思いますが
コレの名前、1 2 3 4なんですよね・・・ この記事書いてて気づきました。
node.item("1")とかやると取ってこれます
先ほどのにitemの色も変えるように書き加えるとこんな感じです
def changeColor(node, rgb):
node.setColor(hou.Color(rgb))
node.item("1").setColor(hou.Color(rgb))
はい、カラフルですね
実用性あるかわかりませんが、エラーなどの表示に良いかもしれません。
というわけで、文字が多い割にネタの少ない記事になってしまいましたが
Parameter Interfaceで使えるPython の話でした
実はまだ
####レイアウトをPythonで保存して複数のHDAで使い回す方法
と
オススメのParameter Interface+Sciprtのパーツ集
の話をしようと思ったのですが
この記事の公開が遅くなってしまったのと、アドカレにまだ空きがありそうなので、別の日に書くことにします
(追記: 24日の記事として投稿しました https://qiita.com/yuya_torii/items/6f07a5708c3f45ad632c)
という事で一旦終わります
質問・ご相談などあればこちらに気軽にどうぞ。
toriivfx@gmail.com