この記事は Houdini Advent Calendar 2017 の15日目の記事です。
Houdini初心者が hou モジュールについて学んだこと、
デキる先輩方の Houdini を python の面から再整理してまとめてみました。
今年のアドベントカレンダーも記事がハイレベルで大変萎縮していますが、気にせず行きます😇
前置き
業務ではMayaを使っていますが、個人で使えるDCCツールを検討した結果、Houdiniを覚えることにしました。
indie版と書籍を買ったものの、覚えるためにイマイチとっかかりが掴めなかったので、
- 3Dツールを知るには、それに搭載されているスクリプト言語を覚えるのが一番近道
- 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
モジュールを使って調べてみます。
>>> import _hou, inspect; print len(inspect.getmembers(_hou))
5182
len()
を外して _hou の中を見てみると、python における private 関数が多く混ざっていて、
Python ライクでない C++ オブジェクトなので、エンドユーザとして使う機会はなさそう。
>>> import hou, inspect; print len(inspect.getmembers(hou))
861
今回見ていくhou はこんな感じです。仮にパレートの法則が当てはまるのであれば、約170個のメソッドを覚えれば、Houdini を使うことができると言えそうです。
比較対象として 攻撃対象になりやすい Maya のコマンド数も調べておきます。
>>> import maya.cmds as cmds; inspect; print len(inspect.getmembers(cmds))
3836
プラグインによる拡張コマンドも含まれているので、参考程度で。
💻 hou モジュールを使っていく
Node
ノードを取得するところから全ての操作は始まるので、ノードの操作をとにかく覚えます。
SOPNode, CHOPNode, DOPNode などは NetworkMovableItemクラス及び 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
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の開発者は気が利いているなと思います。
階層
コンテキスト階層を知る、下記のメソッドが用意されています。
hou.root() == hou.node("/")
hou.pwd() == hou.node(".")
hou.parent() == hou.node("..")
MayaのMELと違って オブジェクトが帰ってくる ので、変数に入れたパスを再代入する際は、
hou.node(VARIABLE.path())
で文字列に変更する必要があります。
その他
名前、色、位置、コメントなど入力できます。
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.")
あくまで、表示されるノードを変更するものなので、最初混乱します。
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
オブジェクトです。
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 へ移行した方が良さそう。
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
オブジェクトを取得・設定します。
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()
からアクセスします。
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 を仕込みます。
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があれば、教えてくださると嬉しいです。