HoudiniのParameter Interfaceで使えるPython ①

この記事は[Houdini Advent Calender 2018](https://qiita.com/advent-calendar/2018/houdini)の１５日目の記事です

今回は、HoudiniのParameter Interfaceで使えるPythonの話を書こうと思います
基本的には自分の経験上で**実装する時にちょっと調べた事**や**普段良く使う構文などのTips** が中心で
できること全体をカバーする内容ではありません。
どうやってやるか調べた事あるけど、わからなくて挫折したネタが見つかるかも。という程度でみて下さい。


## Menu Script
Menu Scriptを使って **inputに応じてMenuの内容を変える方法** を実装してみたいと思います
<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/914479cc-7861-19ab-d1d3-8bfd755f81b3.png" width=640>


まず、Menu Scriptは
**Orderd Menu Type** であれば必ずONになっていますが
**String Typeでも** use Menuにチェックを入れると使えるようになります
<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/33b2b1c0-7497-6cb1-b2fa-7c466369f794.png" width=1280>

ここに書く内容として必ず守らなければいけないのが
かならず **文字列の配列を返す**　ということです。
その処理を書き忘れるとWarningが出まくって面倒な事になります・・

厳密には配列には **Token**と**Label** の２つの要素を **交互に**いれる必要があります
TokenとLabelとは、Menu Itemsを直接指定する時に使うこの時の組み合わせのことです
<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/0bc01b5d-049b-c95b-e01f-5781bce4a4b4.png" width=640>
また、もう１つ注意しなければならないのが、返す配列の中身は

```python
[ [token, label], [token, label] ]
```

では無くて

```python
[ token, label, token, label ]
```
のようにただ交互に繰り返して並べるだけという事です。
tokenとlabelはペアだと思って直感的に前者の書き方をしそうになりますが違います。

---
#### Menu Script Sample

実際にどんな書き方をして要素を作ると簡単かサンプルをあげてみます。
例えば　**Tokenを数字にしてLabelを入れる場合**　は
***enumrate***とかを併用すると便利です

```python
labels = ["box","sphere","grid"] # 使いたいメニューの項目
result = []
for (i,v) in enumerate(labels): # iに数字が入る(0スタート）
  result.extend([i,v])
return result
```
または **TokenとLabelを同じにしたい**　場合は
***zip***とか***sum***を使って

```python
labels=["P","N","Cd"] #使いたいメニューの項目、attributeとかはtoken同じにする場合が多い
return sum(zip(labels, labels),())
```
とかやるとまとまります。

以上を踏まえて、冒頭にあったinputに応じてMenuの内容を変えるサンプルを作ってみます

### 入力ノードのPointAttribute一覧をMenuに表示するサンプル ###
以下のように書きます。

```python
inputs = hou.pwd().inputs() 
result = []
if len(inputs):
    node = inputs[0] # inputは複数あるのでとりあえず１番目の例
    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** が作れます

<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/0b485c15-32f5-bed9-16d6-d9775238c5a1.png" width=640>



## Action Button Script
続いて **ActionButton** にスクリプトを書いてみたいと思います
ActionButtonとはMenuの横でよく見かけるこういうやつです
<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/208f6c87-57af-04e6-4345-a9eded639f44.png" width=480>

Action Buttonの設定はParameter Descriptionの一番右のタブにあります
**とりあえず何か書けばボタンを押した時にそのスクリプトが実行されます**
（何も書かれていないとボタンが出ません）
<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/bf5f2414-ff35-bd43-3841-7537fa6b5a99.png" width=480>

たとえば、ActionButtonを押した時にParameterの値を変化させたい場合は

```python
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**モジュールが便利なのでそれを使いましょう

とりあえず書くとこんな感じです


```python 
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を１つ指定すれば良いのかというとそうではなくて
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欄を使います
<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/44069325-dd85-fb0c-9ce3-b776479ba413.png" width=1280>

まず左側で**Event handler**を定義する必要があります
先ほどのkwargsのリンクと同じページに書いてあるので、順に読んで頂いた方は気づいたかもしれませんが
もう一度リンクを貼っておきます
http://www.sidefx.com/ja/docs/houdini/hom/locations.html#asset_events

おそらくよく使われるのは
OnCreated (HDAを作成した時に実行される）
OnInputChanged　(Inputが変わった時に実行される）
PythonModule (汎用モジュール）
あたりだと思います

**それぞれに役割を持たせてクラスや関数を書くことが出来ます**が
個人的には**PythonModuleにまとめて書く**ことが多いので
そういった使い方を説明します。

さっそくサンプルとして
####入力したオブジェクトのタイプに応じて色を変える
というHDAを作ってみましょう。
HDA自体の作成方法は省略して、Scriptの登録から説明します。

まず色を変える関数を作っておきましょう

```python
def changeColor(node, rgb):
    node.setColor(hou.Color(rgb))
```
名前は何でも良いですが関数として**PythonModuleに**書いておきます

次に入力に応じて切り替えて見ようと思いますが
Polygonなら赤
PackedGeometryなら緑
VDBなら青
にしてみましょうか
*便宜上 １つ目のPrimで判断することとします

**OnInputChanged**イベントを作り、その中に以下のように書きます

```python
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)

<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/62d8478d-b126-0f47-69f7-cc37d2784f2a.png" width=480>


繰り返しになりますが、自分はHDAで使う関数は大体PythonModuleにまとめてしまいます。
この場合、OnInputChangedのイベントからPythonModuleの中にある関数を呼ぶのに
node. **hdaModule()**　というのを使ってますが
このように**PythonModuleにある関数は他のイベントや、イベント外から呼べる**のに対して
OnCreateなど特定のイベント内に書いた関数は他から呼び出せない為
結果的にPythonModuleに全て書いたほうが汎用性が高くなるからです

例として、押したら色が黒になる、というボタンを作ってみましょう
ついでに、**CallbackScript**というのを説明します

#### CallbackScriptからPythonModuleを使う
CallbackScriptはParameterDescriptionのところにあります
<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/ed383eab-23c2-7a09-6a42-468bc4051547.png" width=640>

HscriptとPython両方が使えるので、右端でPythonにしておきます
Pythonはこんな感じに書いてますが

```python
hou.phm().changeColor(kwargs["node"],(0,0,0))
```
先ほど使った**node.hdaModule()**でPythonModuleを呼び出すのを省略して
**hou.phm()**というのが使えます
こういう良く使う構文の省略形を用意してくれてるのがSideFXの良いところですね

結果的に、ボタンを押した時やインプットを切り替えた時に、それぞれ同じ関数を呼び出して使うことが出来ました。
CallbackScriptは１行で済む処理ならそのまま書いちゃうことも出来ますが
**処理を変更したい時**に、OnInputChangedやCallBackScriptに書いた内容を**それぞれ編集するのは面倒**ですよね
なので、なるべくpythonModuleに書いて、同じ関数を呼び出すようにしています。
どんな処理をさせるかとか、どんな引数にするかとかでも違ってくると思いますが、一応おすすめの方法でした。

で、また小ネタなんですが・・（多すぎですが）
Subnetでよく見る**コレ**に**色を付けるとノードのラインの色が変わる**のはご存知の方も多いと思いますが
<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/ba9c1b83-23b5-dec6-736a-70494e338322.png" width=640>
**コレの名前、1 2 3 4なんですよね・・・** この記事書いてて気づきました。

**node.item("1")とかやると取ってこれます**
先ほどのにitemの色も変えるように書き加えるとこんな感じです

```python
def changeColor(node, rgb):
    node.setColor(hou.Color(rgb))
    node.item("1").setColor(hou.Color(rgb))
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/153791/cc5e55f4-9125-ac68-c514-dc415677d1df.png" width=480>


はい、カラフルですね
実用性あるかわかりませんが、エラーなどの表示に良いかもしれません。



というわけで、文字が多い割にネタの少ない記事になってしまいましたが
Parameter Interfaceで使えるPython　の話でした

実はまだ
####レイアウトをPythonで保存して複数のHDAで使い回す方法
と
#### オススメのParameter Interface+Sciprtのパーツ集
の話をしようと思ったのですが
この記事の公開が遅くなってしまったのと、アドカレにまだ空きがありそうなので、別の日に書くことにします
(追記：　２４日の記事として投稿しました　https://qiita.com/yuya_torii/items/6f07a5708c3f45ad632c)


という事で一旦終わります
質問・ご相談などあればこちらに気軽にどうぞ。
toriivfx@gmail.com