ノード作成時に独自パラメーターを追加する方法

本記事は、
[Houdini Advent Calendar 2018](https://qiita.com/advent-calendar/2018/houdini)　21日目の記事です。

# はじめに

ノード生成時に独自のパラメーターを追加する方法を説明します。
（かれこれ５、６年前、h11とかh12の頃にやってた事なので、
　既知の内容ばかりかもですが。。。）


例として、
ropのgeometryノードにパラメーターを追加します。

ここでは、geometry ropが元々持っているパラメーターを、
「Default」と言うタブ（フォルダ）を作ってそちらに全部移動し、
追加で新たに「Backburner」と言う名前のタブを追加します。
（Backburner・・・？死語？と言う名前のDispatcherにSubmitするパラメーターを作ってます）

geometryノードのデフォルトパラメーター　
![addParm0.jpg](https://qiita-image-store.s3.amazonaws.com/0/325415/5bed5428-f3bd-0ca3-5b9e-3951bf2f236e.jpeg)

こちらが変更、新たに追加したタブとパラメーター
![addParm.jpg](https://qiita-image-store.s3.amazonaws.com/0/325415/49b503a9-c659-8c82-a259-b6edceee9dab.jpeg)


#どうやるか？

Houdiniには、
ノードが作成された直後、
用意したスクリプト（python or hscript）ファイルを実行してくれる仕組みがあります。
[Event Script](http://www.sidefx.com/docs/houdini/hom/locations.html)と呼ばれます。

Event Scriptは決まったファイル名にする必要があり、
以下が命名規則となってます。
・OnCreated.py（どんなノード生成時も実行される）　　　　　　　　 
・ノードタイプ名.py
　（ノードタイプ名のタイプのノードが作成された時実行される。
　　これはノード生成時に実行されるもので、
　　デフォルトで、$HFS/houdini/scripts/カテゴリ/ノードタイプ名.cmd　が用意されているノードもあり、
　　独自にノードタイプ名.pyを用意してパスに先に通すと、上記ノードタイプ名.cmdが実行されなくなるので注意。
　　また、HDAの場合実行されない）
・ノードタイプ名_oncreated.py
　（上記ノードタイプ.py実行後、実行される、こちらがノード生成後に実行されるものと捉えられます。
　　HDAの場合、この命名規則のファイルを用意しておけば実行してくれます）


<b>OnCreated.py</b>
HOUDINI_PATHに通したパスの中の、/scripts/pythonに
ファイルを置いておけば実行してくれます。
パスの中で最初に見つかったものだけ実行されます。

<b>ノードタイプ名.py</b>
ノードはカテゴリ分けされており、HOUDINI_PATHに通したパスの中の、
/scripts/カテゴリ名フォルダ/ノードタイプ名.py
にファイルを置いておけばノードタイプに一致するノード生成時に実行されます。
パスの中で最初に見つかったものだけ実行されます。

<b>ノードタイプ名_oncreated.py</b>
HOUDINI_PATHに通したパスの中の以下に置いておけば実行されます。
/scripts/カテゴリ名フォルダ/ノードタイプ名_oncreated.py
HDAの場合、
/scripts/pythonに置いておいても実行してくれますが、
上記scripts/カテゴリ名フォルダの両方おくと後者は無視されます。


と言うことで、
上記ファイルにパラメーターを追加するコードを書いておけば、
ノード生成時に独自のパラメーターを追加しておく事が可能です。

が、

パラメーターを追加するコードをすべてpythonで自分で書いて、
とか、めんどくさすぎる！！！

ので、HoudiniのUIでパラメーターを作成し、
それを生成するpythonのコードを取り出せる機能を使って、
ファイルに書き出し、それを使ってパラメーターの追加を行いたいと思います。


# パラメーター作成とコード書き出し

まずは、Edit Parameter Interfaceでパラメーターを追加（追加方法は割愛）
![editParm.jpg](https://qiita-image-store.s3.amazonaws.com/0/325415/fb8064f0-c373-46a8-025c-ac2726114ffc.jpeg)
![editParm2.jpg](https://qiita-image-store.s3.amazonaws.com/0/325415/e14d6d9b-fca7-239e-d8f2-8fdb7228cd23.jpeg)

次に、作った上記のパラメーターをpythonのコードとして書き出します。

```python:createTab.py
# ノードのオブジェクトを取得
node = hou.node("/out/geometry1")

# Backburnerタブを取得
ptg = node.parmTemplateGroup()
folder = ptg.findFolder("Backburner")

# ファイルに書き出し
with open('f:/bbTab.py','w') as f:
  f.write( folder.asCode(function_name='createTab') )
```

上記の最後、<b>folder.asCode()</b>と言うのが、
Backburner（タブ）とそれに含まれるパラメーターを生成するpythonコードを、
文字列として返してくれる関数で、それをファイルに書き出しています。

<b>function_name='createTab'</b>は、
書き出されるコードを関数として書き出すようにする引数で、
以下書き出されたコード、
<b>createTab()</b>と言う関数が作られています。
function_name=に何も指定しないと、関数が作られず、
パラメーターを作るコードが羅列されたものが書き出されます。

```
def createTab():
    # Code for parameter template
    hou_parm_template = hou.FolderParmTemplate("default_1", "Backburner", folder_type=hou.folderType.Tabs, default_value=0, ends_tab_group=False)
    # Code for parameter template
    hou_parm_template2 = hou.ButtonParmTemplate("submit", "Submit")
    　　・
    　　・
    return hou_parm_template

```


# Houdiniのパラメーターの持ち方

実際のパラメーターを追加するコードの説明に行く前に、
Houdiniのパラメーターの持ち方を説明します。

houdiniは、
個々のノードが<b>parmTemplateGroup</b>と言う入れ物を持っており、
この中にパラメーターとなるオブジェクトを持ってます。

各パラメーターは、
<b>parmTemplate</b>と言うクラスから派生したオブジェクトとなっており、
parmTemplateGroupが、それを持っています。

[parmTemplate class](http://www.sidefx.com/docs/houdini/hom/hou/ParmTemplate.html)
[parmTemplateGroup class](http://www.sidefx.com/docs/houdini/hom/hou/ParmTemplateGroup.html)



# Pythonでノードにパラメーターを追加する方法

以下でサンプルがありますが、順を追って説明すると、
[parmTemplateGroup](http://www.sidefx.com/docs/houdini/hom/hou/Node.html#parmTemplateGroup)

``` python:add_param.py
# ノードを作成（/obj以下にgeometryノードを作成）
node = hou.node("/obj").createNode("geo")

# ノードが持つparmTemplateGroup()（箱）を取得
group = node.parmTemplateGroup()

# My Parmsと言うラベルの付いたフォルダを作成
#（フォルダはパラメーターのタブに当たります）
folder = hou.FolderParmTemplate("folder", "My Parms")
# フォルダにfloat型の myparm という名前のパラメーターを追加
folder.addParmTemplate(hou.FloatParmTemplate("myparm", "My Parm", 1))

# 最初に取得したparmTemplateGroup（箱）にフォルダごと追加
group.append(folder)
```

で、最後にポイントなのですが、
上記group.append(folder) でparmTemplateGroup()で取得した箱にフォルダを追加してるのですが、
この時点ではノードにパラメーターは追加されず、
何の変化もありません。

と言うのも、
このparmTemplateGroup()で取得した箱と言うのは、
あくまで<b>コピー</b>を取得しており、もはや実際のノードがもつ箱とは別物で、
これに何を追加してもノードに反映されることはありません。

ではどうするか、
最後に以下を行うことで、
追加したパラメーターを持つ箱、parmTemplateGroupをノードにセットでき、
めでたくパラメーターを追加することが可能になります。
<b>node.setParmTemplateGroup(group)</b>




# 実際にパラメーターを追加するコード

``` python:/scripts/out/geometry_oncreated.py

# 上記で書き出したモジュールをimport
import bbTab

# 作成されたノードのオブジェクトを取得
node = kwargs['node']

# template group取得
ptg = node.parmTemplateGroup()

# Tab(Defaultと言うラベルの)を作りその中に元々存在するパラメーターを全部入れる
folder = hou.FolderParmTemplate('default', 'Default', parm_templates=ptg.entriesWithoutFolders())
for p in ptg.entriesWithoutFolders():
    ptg.remove(p)

# template groupに上記Default Tabを追加
ptg.append(folder)

# Backburner Tabを作成してtemplate groupに追加
ht = bbTab.createTab()
ptg.append(ht)

# ノードに反映
node.setParmTemplateGroup(ptg)

```


# 最後に

上記で「Backburner」と言う名前のTabを追加できるわけですが、
この方法ですと、
geometryノードだけではなく、様々なノードに同じようなコードで、
同じパラメーターの追加が可能になります。

この方法を使った事例としては、
BackburnerのようなDispatcherに対し、
ropのgeometryやその他キャッシュをとったり、publishと言った処理を投げる用に、
同じパラメーターを追加して投げるようにしてました。

メニューやシェルフにSubmitボタンを登録するという方法も取れますが、
ノードにパラメーターがついていることで、
投げた時の設定も保存されるので、再現性も確保できる利点があるかなと。