hou で覚える Houdini


この記事は [Houdini Advent Calendar 2017](https://qiita.com/advent-calendar/2017/houdini) の15日目の記事です。
Houdini初心者が hou モジュールについて学んだこと、
デキる先輩方の Houdini を python の面から再整理してまとめてみました。

今年のアドベントカレンダーも記事がハイレベルで大変萎縮していますが、気にせず行きます😇


## 前置き
業務ではMayaを使っていますが、個人で使えるDCCツールを検討した結果、Houdiniを覚えることにしました。
indie版と書籍を買ったものの、覚えるためにイマイチとっかかりが掴めなかったので、

1. **3Dツールを知るには、それに搭載されているスクリプト言語を覚えるのが一番近道**
2. **DCCツールでよく使われるコマンドの数は、パレートの法則に近似する**

という経験則(要出典)を用いて、スクリプトから Houdini を覚えることにしました。

Houdiniを含む最近のツールには Python が搭載されています。
言語を新たに学び直す必要なしに、モジュールを覚え直すだけで良く、GUI は PySide を使い回しできて、
OS依存が殆どない等、Python で書くメリットは多いです。
手軽なスクリプト言語から学ぶことで、ソフトの思想をより深く知ることが出来ます。


また、DCCツールの膨大な機能を全て使うことがないのと同様に、よく使うコマンドも全体の2割から多くて3割という印象です。
他で通用したノウハウを応用しやすいかどうかは、新しいソフトを学ぶモチベーションに十分なり得るので、
hou モジュールを使って Python でのコマンドを **全体の20%** 覚えれば、複雑なHoudiniを習得できるだろう、と信じて(騙して?)学習を始めます。 

この記事は、Houdini 16.5 のオンラインリファレンスを参考にしています。
http://www.sidefx.com/docs/houdini/hom/hou/index


## 🐍 どこから Python が使えるのか
メニューから

* Window > Python Shell
* Window > Python Script Editor
* Window > Python Panel Editor

など。今回はシェルで書いています。
ノードやパラメータをシェル内にドラックアンドドロップすれば、その名前がわかるので便利です。

この他にも記事作成中に見つけた PythonSOP というノードがあり、
他DCCツールからから来た人にとって Houdini は可能性の塊ですね。


## ❓ hou モジュールとは？
HoudiniにはビルトインのPythonモジュールが多く搭載されていますが、
Houdiniの機能にアクセスできるモジュールは、**hou** です。

このモジュールの正体は、C++ を Swig でラップしてコンパイルした `_hou.pyd (_hou.so)` を、
Pythonで再ラップして整理されたものです。
余談ですが、OpenMaya と MaxPlus も Swig を使って Python 拡張しています。

これから使って行くコマンドの総数をさっくり知りたいので、`inspect` モジュールを使って調べてみます。

```python
>>> import _hou, inspect; print len(inspect.getmembers(_hou))
5182
```

`len()` を外して _hou の中を見てみると、python における private 関数が多く混ざっていて、
Python ライクでない C++ オブジェクトなので、エンドユーザとして使う機会はなさそう。

```python
>>> import hou, inspect; print len(inspect.getmembers(hou))
861
```

今回見ていくhou はこんな感じです。仮にパレートの法則が当てはまるのであれば、約170個のメソッドを覚えれば、Houdini を使うことができると言えそうです。


比較対象として ~~攻撃対象になりやすい~~ Maya のコマンド数も調べておきます。　

```python
>>> import maya.cmds as cmds; inspect; print len(inspect.getmembers(cmds))
3836
```

プラグインによる拡張コマンドも含まれているので、参考程度で。


## 💻 hou モジュールを使っていく 

### Node
ノードを取得するところから全ての操作は始まるので、ノードの操作をとにかく覚えます。

SOPNode, CHOPNode, DOPNode などは [NetworkMovableItem](http://www.sidefx.com/docs/houdini/hom/hou/NetworkMovableItem.html)クラス及び [Node](http://www.sidefx.com/docs/houdini/hom/hou/Node.html)クラスを継承するので、
それら派生元のクラスが持つメソッドは共通して扱うことができます。
扱っているオブジェクトの継承元を意識しておけば、覚えるメソッドはそこまで多くありません。

```python:node
# サンプルより抜粋

>>> hou.node("/obj")
<hou.Node at /obj>

>>> hou.node("/obj").createNode("geo")
<hou.ObjNode of type geo at /obj/geo1>

>>> hou.node("/obj").createNode("geo")
<hou.ObjNode of type geo at /obj/geo2>

>>> hou.node("/obj/geo1")
<hou.ObjNode of type geo at /obj/geo1>

>>> hou.cd("/obj")

>>> hou.node("geo1")
<hou.ObjNode of type geo at /obj/geo1>

>>> hou.cd("/obj/geo2")

>>> hou.node("../geo1")
<hou.ObjNode of type geo at /obj/geo1>
```

###### CRUD

```python:crud
import hou

geo = hou.node("/obj/geo1") or hou.node("/obj").createNode("geo")
for node in geo.children():
    node.destroy()

line = geo.createNode("line")
merge = geo.createNode("merge")
merge.setInput(0, line)

print merge.inputs()
print line.outputs()

cam_node = hou.node("/obj").createNode("cam")
cam_node.setParms({"resx":1920, "resy": 1080})
cam_node.setDisplayFlag(False)

for node in hou.node(geo.path()).glob("*"):
    node.moveToGoodPosition()
```

`node.children()` でノード下の子供を取得でき、`node.glob()` で文字列パターンによる階層の検索を行います。特に glob は、他のツールでは for と if を組み合わせて文字列検索しなければいけなかったものなので、
SideFXの開発者は気が利いているなと思います。


###### 階層
コンテキスト階層を知る、下記のメソッドが用意されています。

```python:context
hou.root() == hou.node("/")
hou.pwd() == hou.node(".")
hou.parent() == hou.node("..")
```

MayaのMELと違って **オブジェクトが帰ってくる** ので、変数に入れたパスを再代入する際は、
`hou.node(VARIABLE.path())` で文字列に変更する必要があります。


###### その他
名前、色、位置、コメントなど入力できます。

```python:property
hou.node("/obj/geo1").setName("Geo", unique_name = True)

geo = hou.node("/obj/Geo")
geo.setColor(hou.Color(1, 0, 1))
geo.setPosition(hou.Vector2(1, 1))
geo.setComment("I'm Houdiniest.")
```
あくまで、表示されるノードを変更するものなので、最初混乱します。

```python:asCode
with open("/YOUR_SAVE_PATH/sample.py","w") as f:
    f.write(geo.asCode())

### or

hou.hipFile.save(fileneme="/YOUR_SAVE_PATH/sample.hip")
```
ノードをPythonファイル化出来ます。hipファイルを配布せず、bat ファイル的な渡し方に使えますか。

### Parm
ノードを特定したら、そのノードのパラメータを読み書きするために `parm` にアクセスします。
ノードと同様にパラメータも文字列でアクセスします。`eval()` するまで `hou.Parm` オブジェクトです。

```python:parm
node.parm('tx').eval()

node.parm('tx').set(1)
node.parm('sy').set(3)

### or

node.setParms({"tx": 1, "sy": 3})
```
`setParms` に辞書で渡せば一括で変更できます。
`globParms()` で文字列パターンによる parm の取得が行えます。便利です。

###### expression
エクスプレッションに Python 使えるのが嬉しいですね。
複雑になる前に VEX へ移行した方が良さそう。

```python:expression
node.parm("tx").setExpression('ch("ty")')
node.parm("sy").setExpression("sin($F)")

### or

node.setParmExpressions({"tx": 'ch("ty")', "sy": "sin($F)"})
```

###### keyframes
`hou.BaseKeyFrame` クラスを継承した `hou.Keyframe` オブジェクトか、
`hou.StringKeyframe` オブジェクトを取得・設定します。

```python:keyframe
key = hou.Keyframe()
key.setFrame(0)
key.setInValue(0)
parm.setKeyframe(key)

## or

key1 = hou.Keyframe()
key1.setFrame(0)
key1.setInValue(0)
key2 = hou.Keyframe()
key2.setFrame(2)
key2.setInValue(5)
geo.node("line1").parm("points").setKeyframes([key1, key2])

```
`fromJson` もあるので、キーフレームの外部化も楽にできそうです。


### Geometry (SOP)
**Houdini 初心者は SOP から**やるのが良い、というアドバイスを参考に中身を覗いていきます。
SOP コンテキスト下で、`node.geometry()` からアクセスします。

```python:geometry
geo = hou.pwd().geometry()

for point in geo.iterPoints():
  iPoint = point.position()
  index = point.number()
  point.setPosition(
        iPoint + hou.Vector3([0, 1, 0]))

prims = geo.prims()
```
`geo.points()` でポイントのタプルを返すのですが、一度に大量のポイントをメモリに確保すると処理が遅くなってしまうので、`geo.iterPoints()`でジェネレータとして扱います。

`geo.prims()` で hou.Prim クラスを取得、派生クラスの `hou.Face`, `hou.Vertex`, `hou.Polygon` などにアクセスできます。
可能性しか感じませんが、今の自分のレベルでは理解が及ばないので今回は割愛します...


### VEX & Wrangle
最後に、Node, Parm を使って、Wrangle に VEX を仕込みます。


```python:vexWrangle
import hou

geo = hou.node("/obj/geo1")

line = geo.createNode("line")
wrangle = geo.createNode("pointwrangle")
wrangle.setInput(0, line)

wrangle.parm("snippet").set("@P;")


## message	
hou.ui.displayMessage(wrangle)
```
VEXpression は snippet なんですね。シェルにドラックアンドドロップするまでわかりませんでした。

このあたりで、スクリプティングだけで進むことの出来る範囲に限界を感じるので、
VEX や attribute など座学と実践が必要な範囲を、並行して習得していくように方針を変更します。

力尽きた感あります。


## まとめ
今回は、複雑に見える Houdiniを Python から触れていくアプローチを紹介しました。
Python側から見たオペレーションはシンプルで、使うコマンドも簡単な物が多いです。

Maya/Max と比較して、Pythonスクリプティングとプロシージャルなワークフローは相性が良く、
その中身も洗練されたモジュールの印象を受けました。

実際に Houdini上でのオペレーションを効率化する際のアイディアとしては、

* プロジェクトに応じたシーンクリーナー
* ノードのスキャフォールド
* Alembic を使ったパイプライン開発

が思いつきます。オペレータとVEXを追い込んで試行錯誤することが多いソフトなので、
それ以外の自動化したい部分はすぐ見つかりますね。

本業がテクニカルなので、学習の糸口が掴めなかったのですが、記事にまとめることで非常に勉強になりました。
皆様の hou tipsがあれば、教えてくださると嬉しいです。


### 参考
* [理論と実践で学ぶHoudini](https://www.amazon.co.jp/理論と実践で学ぶHoudini-SOP-VEX編-佐久間修一/dp/4862463592)
* [Houdini実践ハンドブックWrangle×Python](https://www.amazon.co.jp/Houdini実践ハンドブックWrangle×Python-魔法の蒸留水-ebook/dp/B07145S267/ref=sr_1_1?s=books&ie=UTF8&qid=1512831195&sr=1-1&keywords=houdini+wrangle)