本記事は、Houdini Apprentice AdventCalendar 2022 15日目の記事になります。
Houdiniでは、Python Viewer Stateを使用してインタラクティブなツールを作成することできます。HDAにPython Viewer Stateを登録すると、ビューポート上での操作によってパラメータを操作したり、独自のHUDを作成したりする事ができ、より便利なHDAの作成が可能になります。また、特定のノードに紐付けずにHoudini自体にPython Viewer Stateを登録することもできます。
今回の記事では、Python Viewer Stateにおいて最も重要な、ライフサイクルイベントハンドラーとUIイベントハンドラーについてご紹介し、マウスなどのデバイスからの入力に応じて処理を行う手順を解説していきます。
▼ 目次
- サンプルファイルについて
- Python Viewer Stateとは?
- Python Viewer Stateの使用例
- 使用ソフトウェア
- イベントハンドラーとは?
- イベントハンドラーを使用した例を作成する
- さいごに
▼ サンプルファイルについて
こちらの記事では、サンプルファイルを配布しています。ファイルは、下記のリンクからダウンロードすることができます。
▼ Python Viewer Stateとは?
Viewer Stateを使用すると、ビューポート上でのマウ スの動き、クリック、キー入力などをどのように扱うか制御することができ、例えば、ビューポート上でマウスホイールを回してオブジェクトの分割数を変更したり、ビューポート上でマウスをスクラブしてタイムラインを進めたりするツールを作成することができます。
HDAにViewerStateを登録する際には、HDAの [ Type properties ] を開き、[ Interactive ] タブの [ State Script ] から簡単に追加することができます。または、特定のノードに依存しないViewerStateをHoudiniに登録することもできます。
以下の記事でもいくつかの例をご紹介しています。また、HoudiniでViewer Stateを登録する方法と、本記事内で使用するViewer State Browserウィンドウや Viewer State Code Generatorについては、この記事でご紹介しております。
▼ Python Viewer Stateの使用例
Python を使用したViewer Stateの使用例を確認したいときは、HoudiniヘルプのPythonで独自のViewer Stateを記述する方法で、独自のViewer Stateの例が紹介されています。Python Viewer Stateについて、どんなことができるのか知りたい方は、こちらも合わせてご覧ください。
▼ 使用ソフトウェア
今回の記事で使用しているOSとHoudiniのバージョンは以下の通りです。
OS:Windows 11 Pro
Houdini:19.5.403 Python 3.9
本記事ではHoudini 19.5を使用しています。
Houdini 19.5以降、Python 3.x がデフォルトで使用されていますが、Houdini 19.0以前のHoudini では環境によってPython 2.xが使用されていることがあります。Python2.x と Python3.x との間には互換性がありませんのでご注意下さい。
▼ イベントハンドラーとは?
イベントハンドラーは、Python Stateで最も重要な部分と言っても過言ではありません。
例えば、ビューポート上でマウスを動かしたり、キーボードを操作したときなどに何かを実行することができます。
このセクションでは、それぞれのイベントハンドラーを簡単にご紹介しています。
詳しく知りたい方は、Houdini ヘルプのイベントハンドラーのセクションも合わせてご覧ください。
https://www.sidefx.com/ja/docs/houdini/hom/python_states.html
イベントハンドラーは、単一の引数で呼び出され、便利なアイテムを含む辞書を使ってコールします。
ヘルプページにあるサンプルコードを見てみると、「kwargs」という文字列が使用されていることが確認できます。
この「kwargs」という文字列を辞書として使用することで、自身のStateに関する情報を得ることができます。例えば、使用しているノードや、右クリックのメニューに何が含まれているか、Stateに関連付けられた様々なフラグなどの情報も利用できます。
辞書に含まれる情報の補足として、Houdini ヘルプには以下のように記載されています。
また、特によく使用するイベントハンドラーとしてライフサイクルイベントハンドラー と Ulイベントハンドラーの2つがあります。
この2つのイベントハンドラーを理解することで、Viewer Stateの実行と、キーボードやマウスによる入力に応じて処理を行うことができます。ここからは、これらのイベントハンドラーについて詳しくご説明いたします。
▼ ライフサイクルイベントハンドラー
https://www.sidefx.com/ja/docs/houdini/hom/python_states.html#lifecycle
ライフサイクルイベントハンドラーによって、Stateが実行されたときや中断されたときなどに特定の処理を行うことができます。
onEnter 開始、onInterrupt 中断、onExit 終了、onResume 再開 といったの4つのイベントハンドラーは、それぞれのタイミングで呼び出され、処理を実行することができます。
onGenerateは既存ノードを使わずにステートに入った時に呼び出されるイベントハンドラーです。Houdiniは、Stateが実行されたときに必ず、onEnter か onGenerate のどちらかをコールします。マウスポインタがビューポート上になくてもこれはコールされます。
onEnter
ユーザーがStateに入ると呼び出されます。
ノードが選択されている状態でエンターキーを押してビューポートに入ると、Stateに入ることができます。
onInterrupt
Stateが中断されたときに呼び出されます。
ウィンドウのフォーカスを失なったときや、ポインタがビューアから抜けた時、ユーザが Volatile ツール(Sキーを押したままにすると表示されるツール)に切り替えた時にStateが中断されます。
onExit
Stateを終了させたときに呼び出されます。
Escキーを押したときや、ビューアーを閉じたとき、別のペインタブに切り替えたとき、ノードを削除したとき、Houdiniを終了させたときなどにStateは終了します。
onResume
Stateの中断が再開されたときに呼び出されます。
ビューアーから抜けたポインタが、再びビューアーに入った際や、 Volatil ツールからStateに戻ったときなどにStateは再開されます。
onGenerate
既存のノードを使用せずにStateに入ると呼び出されます。
既存のノードを使用せずに、例えばシェルフツールスクリプトからStateに入るときなどに使用します。
なお、今回の記事では使用しません。
▼ UI イベントハンドラー
https://www.sidefx.com/ja/docs/houdini/hom/python_states.html#ui_event_handlers
UI イベントハンドラーはViewer Stateにおいて、ライフサイクルイベントハンドラーと共に重要なイベントハンドラーの1つです。マウスの移動や、キーの入力などによって呼び出されます。つまり、ユーザーがビューポート上で何らかの操作をしたときに処理を行うために使用します。
onMouseEvent
マウスの移動/クリック時に呼び出されます。
onMouseDoubleClickEvent
マウスのダブルクリック時に呼び出されます。
onMouseWheelEvent
マウスホイールのスクロール時に呼び出されます。hou.UIEventDevice.mouseWheel()を使用するとマウスホイールの方向を、+1 / -1 で返します。
onKeyEvent
キーイベント発生時に呼び出されます。
onKeyTransitEvent
キー遷移イベント発生時に呼び出されます。
onMenuAction
コンテキストメニューの選択時に呼び出されます。
onMenuPreOpen
メニューステートの更新時に呼び出されます。
onParmChangeEvent
ステートパラメータのイベント発生時に呼び出されます。これは、Stateが中断しているときにも実行することができます。
これは、パラメーターの変更に反応させる際に使用します。
onCommand
汎用コマンドのイベント発生時に呼び出されます。
これは、Stateに固有のアクションを実装する際に使用し、ステートパラメータを設定したり、独自の通知の仕組みを実装することができます。
▼ イベントハンドラーを使用した例を作成する
実際に、Houdini上でイベントハンドラーを使用した例を作成していきます。
ビューポート上でマウスで操作を行ったときに、オブジェクトのTransformの値を変更してスケーリングさせます。
マウスをクリックしながら上下にドラッグすることで、オブジェクトが上下方向にスケーリングされ、左右に動かすと左右方向にスケーリングされるようなツールを作る手順をご紹介します。マウスからの入力をイベントハンドラーを用いて検出し、ノードのパラメーターを変更することでこの処理を可能にします。また、マウスホイールを操作して形状をスムージングさせる機能も実装します。
▼ デジタルアセットを作成する
まず、GeometrySOPに入り、Test Geometry: Pig Head SOPノードを配置します。
テスト用のモデルがビューポート上に表示されます。
次に、SmoothSOPを接続します。
smooth SOP の [ Filter Quality ] は、後で変化が分かりやすいように1に設定しておきます。
[ Strength ] の値は”0”に設定します。
さらに、TransformSOPを接続します。
3つのノードを選択し、画像右上ボタンでサブネットを作成します。
サブネットが作成されると、下の画像のようになります。
作成したサブネットを右クリックして、新規HDAを作成します。
[ New Digital Asset ] のウインドウが開きますので、HDAの各種項目を設定していきます。
アセット作成時の名前や保存先の設定を行います。下の画像は、設定の1例です。
アセット作成時の設定項目についての補足説明
アセットの作成に慣れていない方のために、各項目を簡単にご説明いたします。デジタルアセットはHDAとも呼ばれます。
HoudiniではHDAの開発を行う中で細かくバージョン管理をすることができます。詳しくは、Houdini Helpの以下のページもご覧ください。
デジタルアセットの作成とバージョン制御
・Name Construction
Type Name – アセットの内部名を設定します。
Author – ユーザー名またはスタジオ名を設定します。(Houdini のノード名または他のベンダーのノード名と衝突しないように作成者の設定をします。)
Branch – 開発サイクルに関する情報を入力します。「dev」「test」「main」等、推奨される文字列から選択することもでき、必要に応じて設定します。
Version – 初期バージョンの値を設定します。
・Tab Menu
Menu Entry – Tabメニューを押したときに表示されるカテゴリを設定します。
Asset Label – Tabメニューで表示されるアセットの名前を設定します。
・Save To
Library Path – アセットの保存先パスを設定します。
Library Filename – アセットのファイル名を設定します。
デジタルアセットの作成方法と、作成したデジタルアセットを別の場所に保存する方法については以下のブログ記事も合わせてご覧ください。
デジタルアセットを作る
[ Create ] をクリックすると、[ Type Properties ] ウインドウが開きます。
ここでは、作成したアセットのプロパティ設定を行うことができます。
[ Parameters ] タブを開き、Viewer Stateで使用したいパラメーターをここで設定します。
左側の [ Create Parameters ] から、[ Float ] をドラッグアンドドロップで右側の [ Exisiting Parameters ] に入れます。
[ Parameter Description ] の [ Name ] と [ Label ] を設定します。
アセットにパラメータを追加する方法はもう一つありますので、そちらもご紹介します。
アセットに入り、ノードのパラメータの値を直接 [ Exisiting Parameters ] にドラッグアンドドロップする方法です。
ここでは、[ Scale ] の値を [ Exisiting Parameters ] にドラッグアンドドロップします。
追加したパラメータの、[ Name ] と [ Label ] を “scale”にします。
一度、[ Type Properties ] を [ Apply ] [ Accept ] で閉じます。
アセットのパラメータを確認し、 [ Smooth ] を [ Copy Parameter ] でコピーします。
アセット内に入り、Smooth SOP の [ Strength ] に [ Paste Relative Reference ] で相対参照されるように貼り付けます。
アセットの [ Scale ] の値を変更することで、オブジェクト自体のスケールが変更されるようになったことを確認して下さい。
同じように、[ Smooth ] の値も適用されることを確認して下さい。
▼ Code Generatorでコードを生成する
ここまで確認できたら、デジタルアセットを右クリックして [ Type Properties ] を開き、[ State Scripts ] タブの [ interactive ] タブから、[ new ] ボタンをクリックしてViewer Stateを作成していきます。これにより、ビューポート上でのインタラクティブな操作が可能になります。
[ Viewer State Code Generator ] が開きます。
以下のように設定を行いました。[ Name ] や [ Label ]はお好みで設定して頂いて構いません。
[ Samples ] は [ Blank ] を選択し、[ Event Handlers ] は以下の画像のようにチェックを入れて [ Accept ] で決定します。
[ Type Properties ] にコードが生成されたことが確認できます。
[ Type Properties ] 上でコードを編集しても構いませんが、外部のコードエディタを使用することでより便利にコードの編集ができます。今回の記事ではVSCodeを使用しますが、好みのコードエディタをHoudii に設定する手順は以下の記事をご参考ください。
Visual Studio Codeなどの外部エディタを利用する
[ TypeProperties ] を開かずにデジタルアセットを右クリックし、[ Edit Extra Sections Source Code ] をクリックすることで外部のエディタを使用してコードの編集ができます。ただし、このメニューを利用するには、Side FX Labs がインストールされている必要があります。
SideFX Labsについては以下の記事も合わせてご覧ください。
SideFX Labs Tool トップページ
ノードに存在するすべてのコードのセクション一覧が表示されます。
[ Viewer State Module ] を選択して [ Accept] クリックしてください。
[ Type Properties ] のからもコードエディタを起動できます。
すると、設定したエディタが起動するので、コードを見ていきましょう。
ここに表示されるのは、先ほど [ Viewer State Code Generator ] で生成したコードです。
まずは、コードの順番を見やすくするために、並べ替えを行います。
Viewer Stateに入ったとき、onEnter コールバックから始まり、onExit コールバックで終了します。
この流れをわかりやすくするために onExit のコールバックを onResume の後に移動します。
移動後は下の画像のようになります。
▼ ログでライフサイクルイベントハンドラーの動作を確認する
self.log(“文字列”) を使用することで、ログをコンソールに表示することができます。(””で囲む文字列は任意の文字列です。)
onEnter , onExit , onInterrupt , onResume のそれぞれでログが出力されるようにして、ViewerStateのライフサイクルイベントハンドラーの動作を確認してみます。下の画像のように、self.log(“文字列”) を追加します。
保存してコードエディターを閉じ、動作を確認してみます。
コードを実行するときには、保存してコードエディタを閉じてから実行する必要があります。
ログを確認するために、[ Viewer State Browser ] を開きます。
Viewer Stateを開始するには、ノードを選択し、ビューポート上でエンターキーを押します。すると、Viewer Stateが開始され、ログが確認できます。Stateに入ったときや中断されたとき、マウスが動かされたときなどにログが更新される様子が確認できます。
▼ マウス座標を取得して利用する
次に、マウスの座標をログ出力できるようにします。
下の画像のように、UIイベントハンドラーである onMouseEvent にコードを追加します。
保存してコードエディタを閉じ、Viewer Stateを実行すると、マウス座標がログとして更新されることが確認できます。
マウスを動かすたびにログが更新されると分かりにくいので、確認ができたらこの部分はコメントアウトしておきます。
改めて、Viewer Stateを実行し、ログを見ながらViewer State の ライフサイクルイベントハンドラーの動作を確認します。
ビューポート上でエンターキーを押してStateに入ったときには「’onEnter’」が表示されますし、ビューポート上からカーソルが出たら「’onInterrupt’」、またビューポート上に戻ったら「’onResume’」が表示されます。また、Escキーを押してViewer Stateが終了したときには、「’onExit’」が表示されます。ノードを削除した際も同様です。
ログを確認することで、それぞれのライフサイクルイベントハンドラーがどのような条件で実行されるのかを確認することができました。
先程コメントアウトした、マウスカーソルの座標を取得している部分について解説します。
少し上の、”dev = ui_event.device()”の部分に注目してください。ここでデバイスから得た情報を取得しています。
”self.log(dev)”を追加して、”dev”が何の値をもっているのかを確認してみます。
保存してViewer Stateを実行し、マウスをビューポート上で動かしてみます。
[ ViewerStateBrowser ] を確認すると、”dev”にはデバイスから取得した様々な値が含まれていることがわかります。
この中にはマウスの座標も含まれていますので、”dev.MouseX()”などと記述することで”dev'”からマウス座標を取り出しています。
この行を確認すると、”dev”からマウス座標の値を取り出すために”dev.MouseX(), dev.mouseY()”と記述をしています。
また、”dev.isLeftButton()”でマウスの左クリックが押されているかどうかの情報を取得しています。
こうして、ViewerStateBrowserでログの出力を確認するとこのようにマウス座標を確認することができます。
ログで動作の確認ができたら、この部分はコメントアウトしておきます。
次に、マウスカーソルの操作に合わせてオブジェクトのスケールを変更できるようにしていきます。
処理にマウスの座標を利用したいため、初期化関数 “def __init__” 内で変数の定義をします。
自身のノードの値を参照するために、”self.node = None” と記述します。
また、”self.mousecoordinate = [ 0 , 0 ]” と記述して、x, y 座標に対応させるために2つの値を持たせておきます。
“onEnter” の “node = kwargs[“node”] ” を下の画像のように書き換え、ここで取得した自身のノードを onMouseEvent のイベントハンドラー 内で使用します。
下の画像のように”onMouseEvent”のイベントハンドラーにコードを追加して、onMouseEvent内でこの変数の値を更新できるようにします。ここでは、デバイスから取得した値を先ほど作成した”mousecoordinate”の変数に格納しています。
そして、自身のノードの”scale”に値を入れることで、オブジェクトのスケールを更新します。
”node.parmTuple(“scale”).set()”で自身のノードの”scale”パラメータの値を変更しています。ここでは、”.set(x,y,z)”で任意の値を入れる必要があり、マウスから取得した値を”mousecoordinate”の変数に格納し、”mousecoordinate”の値を”scale”に代入しています。Z軸に関して今回は更新する処理を行わないため、常に”1″の値を入れています。
実行して動作を確認すると、スケールが大きすぎることが分かります。
値が大きすぎたので”modifier”という名前の変数を作成し、”500”という値を入れておきます。
“scale”に代入する値を”modifier”で割ることで適切な大きさになるようにします。
これで実行してみると、適切な大きさでスケールされるようになりました。
▼ マウスのクリックを使用する
次に、マウスのクリックを判定する方法をご紹介します。
以下のHoudiniヘルプのページから、サンプルコードを引用します。
Python state user interface events
“ui_event.reson()”を使用することで、マウスクリックの状態に応じた処理を記述できます。
これは、マウスのボタンをクリックしたとき、マウスのボタンを押し続けたとき、マウスのボタンを押し続けた状態でドラッグしたとき、そして、マウスのボタンが離されたときをそれぞれ判定できます。また、print(”文字列”)を記述しているので、コンソール上にログを表示させることができます。
このままサンプルコードを実行すると、マウスクリックやドラックなどに合わせてコンソールにログが表示されます。
次に、コードを下の画像のように編集して、マウスをクリックしてドラッグしたときにのみ、スケールの値が更新されるようにしてみます。
また、コメントアウトしていた不要なコードはこのあたりで削除しておきます。
マウスをクリックせずに動かしたときには何も起きなくなりました。
ビューポート上をクリックしてドラッグしながら動かしたときにオブジェクトのスケールが変化するようになります。
次に、modifierの値をマウスのクリックするボタンによって変更するようにしてみます。
例えば、このようにすると左クリックで500、中ボタンクリックで1000、右クリックで2000というようにmodifierの値が変更されるようになります。
使用するマウスのボタンに応じて、3段階の拡大率でオブジェクトのスケールを操作することができるようになります。
▼ マウスホイールの値を取得して使用する
最後に、マウスホイールの操作でオブジェクトのスムージングを行えるようにします。
Houdiniヘルプのコードを引用して実装していきます。
Python state user interface events
UIイベントハンドラーである、”onMouseWheelEvent” を使用することでマウスホイールの値が変更されたときに特定の処理を実行することができます。
ここでは、先程までに作成してきたコードに合わせるため、”device” を ”dev”に変更するなど、変数名などを少し変更して引用しています。”dev.mouseWheel()” で、マウスホイールの値を取得することができます。これを ”print(“Scroll =”, scroll)” でログ出力させます。
実行すると、マウスホイールの回転方向に合わせて、Scroll = +1.0 、Scroll = -1.0 のように表示されます。
マウスホイールの値は基本的に、回転方向に応じて+1.0 や -1.0 という形で取得されます。
マウスホイールから取得した値をスムージングの値にに適用したいため、自身のノードを参照して”Smooth”に値を入れられるようにしていきます。以下のようにコードを追加していきます。
実行してみると分かりますが、この状態だと”Smooth”の値を 1か 0に変更するだけです。
そこで、新たに”smoothparm”という名前の変数と”current”という名前の変数を作成し、”current”に現在の”Smooth”の値を取得して代入します。取得した値に”scroll”の値を足し合わせ、スケールの時と同様にsetで値を更新することで、値を増加させたり減少させたりすることができるようにしていきます。
なお、今回の例では”smooth”の値はfloat型ですので、float型を指定していますが、他の型の変数を使用する際、例えばint型のパラメータを使用する際には”Float”と記述しているところを”Int”に変更してください。
これにより、マウスホイールでオブジェクトのスムージングをコントロールできるようになりました。
最後に、下の動画が今回の記事で作成したデジタルアセットを実行した様子です。
▼ さいごに
この記事では、Viewer Stateでマウスからの入力を使用する手順についてご紹介してきました。ライフサイクルイベントハンドラーと、UIイベントハンドラーについて ご理解いただけましたでしょうか。
ViewerStateを使用すると、マウスやキーボードなどの操作に合わせてインタラクティブな動作を行うことの出来るHDAの作成が可能になります。本記事で紹介した内容も、応用すれば便利なデジタルアセットの作成に繋がるかと思います。ぜひご活用ください。
Houdini の Viewer Stateについて、色々と勉強しながら記事を執筆させていただいております。私自身、HoudiniでPythonを扱うことについては初心者ですので今後とも皆様とともに勉強させていただきながら記事を更新してまいります。今後ともよろしくお願いいたします。