Houdini 20.0 Pythonスクリプト

Pythonで独自のViewer Stateを記述する方法

Viewer Stateは、ビューア内でのマウスの動き、クリック、キーといった解釈の方法を制御します。

On this page

概要

Viewer Stateは、ビューポート内でのインタラクティブな操作を制御することができます。 例えば、 RotateツールはViewer Stateです。 Handlesツールは、現行ノードに関係したViewer Stateにアクセスすることができます。 Houdiniは、Pythonで独自のViewer Stateを作成して登録することができます。

独自のViewer Stateの例:

Houdiniデジタルアセットには、Viewer Stateを指定することができます(例えば、そのタイプのノードが現行になった時に Handlesツールを使用させることができます)。 結局のところ、(Inspectionツールのようにノードを作成しないツールの)シェルフツールスクリプトでは、hou.SceneViewer.setCurrentStateをコールすることで、プログラム的にステートを変更することができます。

コードを今すぐにでも試したいのであれば、以下のViewer Stateの実装方法を参照してください。そこでは、最低限のViewer Stateの実装、登録、実行の方法について説明しています。

Tip

Viewer Stateの動作を見たいのであれば、$HH/viewer_states/examples下にあるデモシーンとアセットファイルをチェックしてください。 これらのサンプルは、Viewer Stateの機能の詳細が網羅されています。

制限事項

  • 現在のところ、Pythonステートは、次のコンテキストで利用可能です: SOP (ジオメトリ), OBJ , DOP , LOP のレベル。

  • ハンドルをノードなしステートにバインドすることはできません。

ステート名

ステートは、 内部名 とUIで表示する人が解読可能な ラベル を持ちます。 新しく独自のステートを作成する場合、その内部名は固有でなければなりません。もし2人の作成者が同じステート名を使用してしまった場合、どちらかのステートは登録に失敗します。 あなたが作成したステートまたはアセットがいつの日か他のユーザ/スタジオで共有もしくは製品として販売することを考慮すれば、適切な固有名になるようにちゃんと時間をかけて決めてください。

Houdiniはアセット毎に自動的に汎用ステートを作成するということも相まって、既存のノードタイプの名前をステート名として使用することはできません。

  • ステートを2つ以上のノード間で共有したい場合やノードに関連付けたくない場合、必ずどのアセットの汎用ステートとも干渉しないようなステート名を付けなければなりません。この一番良い方法は、アセットで使用しているのと同じネームスペースを組み込むことです。

    例えば、あなたがExample.com映画スタジオで働いていて、アセットのネームスペースの接頭辞にexamplecom::を使用していると仮定すると、“scrub”ステートを作成する時は、そのステート名をexamplecom::scrubにしてください。

  • ステート名には、ノードタイプ名やノード名と同じ文字制限がありません。ステート名は多かれ少なかれ任意の文字列にすることができます。

  • ファイル/ディレクトリ名として名前を使用する必要が出てくる場合があります(例えば、コードをファイルに保存する場合)。

ツール + ステート vs. 自己完結型ステート

ほとんどじゃなくても多くの“ネイティブ”のHoudiniノードステートは自身の選択を制御することもノードを作成することもしません。 代わりに、それらのノードステートは、選択の促しとノード作成をシェルフツールに頼っています。 それらのノードステートは単にハンドルの表示を担っているだけです。

独自のPythonステートを使ってこのワークフローを使用することもできます。特に、そのステートがアセットに密接に関係している時です。 選択を促してノードを作成する(そして、その選択をノードの Group フィールドに設定する)シェルフツールスクリプトを書くことができます。

別の方法としては、ステートを自己完結型にすることができます。 つまり、ノードをビューアから呼び出した時にそのノードを作成して、そこに独自のセレクターを持たせることができます。

(ステートにノードが不要な場合(例えば、検査タイプのツール)、その詳細はノードを使用しないステートの書き方を参照してください)

  • 2つ以上のタイプの選択を要求する必要がある場合(例えば、“いくつかカーブを選択”した後に“それらのカーブ上のいくつかのポイントを選択する”場合)、ツールスクリプトを使用してください。現在のところ、Pythonステートは、1個のタイプの選択のみ受け入れることができます。

  • コードがノードを作成 する前に 何が選択されているのかを知る必要がある場合、ツールスクリプトを使用してください。

ステートでノードインスタンスを制御する詳細は、ノードの扱い方を参照してください。

Houdiniにステートをインストールする方法

HoudiniでViewerステートを作成してインストールする最も便利で簡単な方法は、Viewer State Code Generatorダイアログを使用することです。 このコードジェネレータは、即座に完全に機能するステートを用意し、ゼロからViewerステートを記述するのに必要なすべての詳細を理解するのに役立ちます。

現在のところ、Houdiniで利用可能なカスタムステートを作成する方法が2通りあります: アセットにステートのコードを埋め込む方法(HDA Viewerステート)とHoudiniパスの正しいディレクトリにそのコードを含んだPythonファイルを配置する方法です。

アセットにステートを埋め込む方法

HDA Viewerステートを作成するには、Operator Type PropertiesウィンドウのViewer State Editorタブで利用可能なコードジェネレータを使用します。

以下の手順は、アセットを作成し、そこに最低限のステートを試せるようにコードを記述します。

  1. ステートを追加できるようにアセットの作成から始めます。サンプルをシンプルにするために、OBJアセットを使って始めましょう。

    Note

    空っぽのアセットから始めたい場合は、に従うことでSOPアセットを、に従うことでOBJアセットを作成することができます。 コードジェネレータで生成されたViewerステートは、どちらのアセットタイプでも動作します。

    実験できるように“空っぽ”のSOPアセットを手軽に作成するには:

    1. オブジェクトレベルで、⇥ Tabメニューを使ってGeoオブジェクトを作成します。

    2. geo1ノードをダブルクリックして、ジオメトリネットワークの中に入ります。

    3. ⇥ Tabメニューを使ってSubnetworkノードを作成します。

    4. subnet1ノードを右クリックして、__Digital Asset → Create New__を選択します。

    5. Type NamestatedemoAsset LabelState DemoLibrary PathEmbedded in .hip Fileに設定します。

      ライブラリの場所をEmbeddedに設定することで、アセットライブラリではなく、現行シーンファイルにアセットが保存されます。

    6. AuthorBranchVersion のチェックボックスのチェックを外します。

    実験できるように“空っぽ”のOBJアセットを手軽に作成するには:

    1. オブジェクトレベルで、⇥ Tabメニューを使ってSubnetworkノードを作成します。

    2. subnet1ノードを右クリックして、__Digital Asset → Create New__を選択します。

    3. Type NamestatedemoAsset LabelState DemoLibrary PathEmbedded in .hip Fileに設定します。

      ライブラリの場所をEmbeddedに設定することで、アセットライブラリではなく、現行シーンファイルにアセットが保存されます。

    4. AuthorBranchVersion のチェックボックスのチェックを外します。

  2. そのアセットのType Propertiesウィンドウを開きます(アセットタイプのインスタンスを右クリックして、 Type Properties を選択します)。

  3. Interactive|State Script タブをクリックします。

  4. New… ボタンをクリックして、ステートコードを生成します。

  5. onMouseEvent イベントハンドラーを選択して、 Accept をクリックします。

Viewer State Browserツリーにリストされた関数ステートがStatedemoとして表示されているはずです。

import hou
import viewerstate.utils as su

class State(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        """マウスイベントを処理します
        """
        ui_event = kwargs["ui_event"]
        dev = ui_event.device()
        self.log("Mouse:", dev.mouseX(), dev.mouseY(), dev.isLeftButton())

        # イベントを消費するにはTrueを返さなければなりません
        return False


def createViewerStateTemplate():
    """登録するViewerステートテンプレートを作成して返すのに必須となるエントリーポイント"""

    state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
    state_label = "Statedemo"
    state_cat = hou.objNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon(kwargs["type"].icon())

    return template

ステートクラスに機能を追加する方法の詳細は、以下のステートを実装する方法を参照してください。

このステートをテストするために、ネットワークエディタ内でそのアセットを選択します。マウスをシーンビューア内に移動させて、Enterを押します。 マウスを動かすと、そのマウス座標のログがViewer State Browserコンソールに記録されているはずです。

Houdini Pathからステートを読み込む

File Viewerステートを作成するには、Viewer State Browserウィンドウのコードジェネレータを使用します。

以下の手順では、複数のアセット間で“共有”可能なFile Viewerステートを作成します。 File Viewerステートは、Houdiniを起動した時に自動的に登録されます。

  1. New Pane Tab Type ▸ Viewer State Browser メニューからViewer State Browserウィンドウを開きます。

  2. ツールバーからObjectカテゴリを選択します。

  3. File ▸ New State… メニューからViewer State Code Generatorを開きます。

  4. Name フィールドで、 ステートの名前 としてstatedemoと入力します。

  5. onMouseEvent イベントハンドラーを選択して、 Accept をクリックします。

この新しいFile Viewerステートを$HOUDINI_USER_PREF_DIR/viewer_states/statedemo.pyとして保存すると、Viewer State BrowserツリーStatedemoとしてリストされます。

これで、Viewer State Browserツリーにリストされた関数ステートがStatedemoとして表示されているはずです。

import hou
import viewerstate.utils as su

class State(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer


    def onMouseEvent(self, kwargs):
        """マウスイベントを処理します
        """
        ui_event = kwargs["ui_event"]
        dev = ui_event.device()
        self.log("Mouse:", dev.mouseX(), dev.mouseY(), dev.isLeftButton())

        # イベントを消費するにはTrueを返さなければなりません
        return False


def createViewerStateTemplate():
    """登録するViewerステートテンプレートを作成して返すのに必須となるエントリーポイント"""

    state_typename = "statedemo"
    state_label = "Statedemo"
    state_cat = hou.objNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon("MISC_python")

    return template

このステートをテストするために、ステートを追加したいアセットを探すか、で空っぽのSOPアセットを作成するか、でOBJアセットを作成します。サンプルをシンプルにするために、OBJアセットを作成します。

実験できるように“空っぽ”のSOPアセットを手軽に作成するには:

  1. オブジェクトレベルで、⇥ Tabメニューを使ってGeoオブジェクトを作成します。

  2. geo1ノードをダブルクリックして、ジオメトリネットワークの中に入ります。

  3. ⇥ Tabメニューを使ってSubnetworkノードを作成します。

  4. subnet1ノードを右クリックして、__Digital Asset → Create New__を選択します。

  5. Type NamestatedemoAsset LabelState DemoLibrary PathEmbedded in .hip Fileに設定します。

    ライブラリの場所をEmbeddedに設定することで、アセットライブラリではなく、現行シーンファイルにアセットが保存されます。

  6. AuthorBranchVersion のチェックボックスのチェックを外します。

実験できるように“空っぽ”のOBJアセットを手軽に作成するには:

  1. オブジェクトレベルで、⇥ Tabメニューを使ってSubnetworkノードを作成します。

  2. subnet1ノードを右クリックして、__Digital Asset → Create New__を選択します。

  3. Type NamestatedemoAsset LabelState DemoLibrary PathEmbedded in .hip Fileに設定します。

    ライブラリの場所をEmbeddedに設定することで、アセットライブラリではなく、現行シーンファイルにアセットが保存されます。

  4. AuthorBranchVersion のチェックボックスのチェックを外します。

  1. アセットのType Propertiesウィンドウを開きます(アセットタイプのインスタンスを右クリックして、 Type Properties を選択します)。

  2. Node タブをクリックします。

  3. Default State フィールドにステート名を設定します(上記のコードの例だと、これはstatedemoです)。

  4. Type Propertiesウィンドウの下部にある Accept をクリックします。

    • アセットがロックされていた場合、Houdiniは、変更を保存できるようにアセットのロックを解除するように促します。

  5. このステートをテストするために、ネットワークエディタ内でそのアセットを選択します。マウスをシーンビューア内に移動させて、Enterを押します。 マウスを動かすと、そのマウス座標のログがViewer State Browserコンソールに記録されているはずです。

    • ビューアの上部にあるツールバーは、左側にこのステートのラベルを表示します。

Houdiniを起動すると、ステートテンプレートにアクセスして登録を実行するためのcreateViewerStateTemplateがコールされます。 省略した場合、Houdiniは単にステート登録をスキップします。

ステートクラスに機能を追加する方法の詳細は、以下のステートを実装する方法を参照してください。

Tip

Houdiniセッション中でFile Viewerステートをリロードするには、Viewer State Browserツリーにリストされているステート名の上でして、コンテキストメニューから Reload を選択します。 これによって、Houdiniを再起動することなくステートへの変更をテストすることができます。

Pythonを使って、ステートの名前でhou.ui.reloadViewerStateをコールすることで、ステートをリロードすることもできます。

ステートを実装する方法

以下のセクションでは、ステートを実装するクラスに追加可能なハイレベルのメソッドの概要について載せています。

Note

Houdiniには、viewerstate.utilsというPythonモジュールが用意されています。 このモジュールには、Viewerステートのインストールをサポートしたり、独自のステートの実装に役立つドキュメント化された様々なユーティリティ関数/クラスが含まれています。 このモジュールは、$HHP/viewerstateフォルダに配置されています。

特定の機能の実装方法のガイドに関しては、以下のページを参照してください:

イニシャライザ(必須)

def __init__(self, state_name, scene_viewer):

state_name

このステートが登録されたステート名の文字列。

scene_viewer

ツールが作用しているシーンビューアを表現したhou.SceneViewerオブジェクト。 このオブジェクトには、ステートを実装する際に使用可能な便利なメソッドがたくさんあります。例えば、hou.SceneViewer.currentGeometrySelectionhou.SceneViewer.setCurrentGeometrySelectionです。

一般的には、他のメソッドが引数を必要とする場合に、それらの引数をオブジェクトアトリビュートに格納したいことでしょう:

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    # イベントハンドラー
    # ...

イベントハンドラー

イベントハンドラーは、単一引数、色々な便利アイテムを含んだ辞書を使ってコールします。 この辞書には、典型的には(必ずとは限りません)以下のアイテムが含まれています:

node

現行ステートで作用しているノードを表現したhou.OpNodeインスタンスを含んでいます。

state_parms

現行ステートにバインドされたステートパラメータを表現した名前を含んでいます。 この辞書は、パラメータステートを変更する際に使用します。 詳細は、ここを参照してください。

state_flags

ステートに関連した色々なフラグを含んだ辞書。 ステートフラグは、kwargs引数を介して、すべてのステートハンドラーで設定することができます。

フラグ

メモ

mouse_drag

(による)マウスドラッグイベントを生成するかどうかを制御します。 デフォルトでは、マウスドラッグイベントを生成します(True)。

mouse_dragFalseの時、マウスドラッグイベントを生成しません。セレクターがアクティブな場合、選択可能なエレメントはハイライトされません。

あなたのステートではマウスドラッグイベントが不要な場合は、mouse_dragFalseに設定して、オーバーヘッドを回避すると良いでしょう。

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onEnter( self, kwargs):
        kwargs['state_flags']['mouse_drag'] = False
        ...                

redraw

このフラグは、Trueに設定すると、マウス移動またはマウスホイールのイベントが発生した時に、ビューポートの再描画をトリガーします。

デフォルトでは、ビューポートは常に再描画します。大規模シーンのパフォーマンス問題を軽減したいのであれば、あなたのステートがでビューポートの再描画が不要な時にはredrawFalseに設定してください。

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent( self, kwargs):
        kwargs['state_flags']['redraw'] = False
        if __some_redraw_test__:
            kwargs['state_flags']['redraw'] = True
        ...                

indirect_handle_drag

このフラグは、True(デフォルト)に設定すると、ビューポート内で(を押したままにすることで)間接ハンドルドラッグが有効になります。 このフラグがFalseに設定されていれば、間接ハンドルドラッグが無効になります。

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onEnter( self, kwargs):
        # 間接ハンドルドラッグを無効にします。
        # ビューポート内でのMMBドラッグによってハンドルパラメータが修正できなくなります。
        kwargs['state_flags']['indirect_handle_drag'] = False
        ...

exit_on_node_select

このフラグは、別のノードが選択された時に、ステートを抜けるのか、それともアクティブのままにするのか決めます。 True(デフォルトの挙動)に設定すると、別のノードが選択されるとステートが抜けます。 Falseに設定すると、別のノードが選択されてもステートはアクティブのままになります。 exit_on_node_selectフラグは、ノードなしステートとノードありステートのどちらでも使用することができます。

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onGenerate( self, kwargs):
        # ノード選択が行なわれても、このノードなしステートがアクティブのままになるように設定します。
        kwargs['state_flags']['exit_on_node_select'] = False
        ...                

interrupt_state

Volatileステートが有効な時または空っぽの文字列の時にPythonステートを中断するステートの名前。

以下のテーブルには、ステートクラスを定義可能なイベントハンドラーを載せています。

ライフサイクルのイベントハンドラー

メソッド名

コールされるタイミング

メモ

onEnter

ネットワークからステートが実行された時

このメソッドは、ユーザが新しいノードを作成、または、既存ノードを選択してビューポート内でEnterを押したことによってステートがアクティブになった時にコールされます。

onInterrupt

ステートが中断された時

このメソッドは以下の時にコールされます:

  • ウィンドウがフォーカスを失なった時。

  • ポインタがビューアから抜けた時(メニューの上に移動した時も含む)。

  • ユーザが“Volatile”ツール(例えば、((S)を押したままにすると表示されるツール)を押してVolatile選択ツールに切り替えた時)。

Note

kwargs['interrupt_state']には、ステートを中断しているVolatileステートの名前が格納されます。

onExit

ステートを終了させた時

このメソッドは以下の時にコールされます:

  • ユーザまたはHoudiniが別のツールに切り替えた時。

  • ユーザがビューアを閉じた時、または別のペインタブに切り替えた時。

  • ユーザがHoudiniを終了した時。

Note

ノードを削除すると、kwargs['node']は存在することができません。

onResume

中断を再開した時

このメソッドは以下の時にコールされます:

  • ポインタがビューア内に再度入った時。

  • ユーザが“Volatile”ツールからこのステートに戻った時(例えば、Volatile選択ツールを使用した後にSを離した時)。

Note

kwargs['interrupt_state']には、ステートを中断したVolatileステートの名前が格納されます。

onGenerate

既存ノードを使わずにステートに入った時

このメソッドは、ユーザが既存ノードから ではなくて 、例えば、hou.SceneViewer.setCurrentStateをコールするシェルフツールスクリプトからステートに入ることでステートがアクティブになった時にコールされます。 ノードを使わずに動作するステートに関する詳細は、ノードなしステートを参照してください。

アセットに関連付けられたステートに関しては、理論的には、このメソッドでノードを作成することができますが、代わりにツールスクリプトでノードを作成することを強く推奨します。 詳細は、Pythonステートでノードを扱う方法を参照してください。

このメソッドに渡される辞書には、nodeアイテムが含まれていません。 hou.SceneViewer.pwdを使用することで、ビューアに相当するネットワークロケーションを取得することができます。

補足メモ:

  • Houdiniは、ツールがアクティブになった時にonEnter(ステートが既存ノードに対して動作する場合) または onGenerate(ステートが新しいノードを作成する場合)の どちらか をコールします。両方をコールすることはありません。onEnterに渡される辞書にはnodeアイテムが含まれており、既存ノードをhou.OpNodeオブジェクトとしてアクセスすることができます。詳細は、ノードの編集を参照してください。

  • Houdiniは、ステートを開始する時にマウスポインタがビューア上になくてもonEnter/onGenerateをコールします。マウスポインタ周辺に何かの視覚的な表示を行ないたいのであれば、それらを表示できるようにonMouseEventコールを待ってください。

  • ステートを“中断”している間は、UIイベントは受信されません。

  • onInterruptコールの後にそれに呼応するonResumeが必ず実行されるようにすることはできません。ステートを中断している間にユーザがステートを終了すると、onResumeコールではなくonExitが受信されます。

  • ユーザがステートのコンテキストメニューを開いた場合、マウスポインタをそのメニュー上に動かすとonInterruptが受信され、そのメニューから出るとonResumeコールが受信されます。

  • ステートが現行で中断された際にHoudiniがバックグラウンドにあれば、マウスポインタがビューア上にあったとしても、ユーザから何かの入力(例えば、マウスの移動)がない限りは、ステートはonResumeコールを受信しません。

UIのイベントハンドラー

詳細は、UIイベントの制御を参照してください。

これらのメソッドに渡される辞書には、以下の追加項目が含まれています:

ui_event

イベントに関する情報を持ったhou.ViewerEventインスタンスを含んでいます(例えば、マウスイベントに関しては、現行マウス座標、ボタンがクリックされたかどうか)。

メソッド名

コールされるタイミング

メモ

onMouseEvent

マウスを移動/クリックした時

マウスの制御を参照してください。

onMouseDoubleClickEvent

マウスをダブルクリックした時

マウスの制御を参照してください。

onMouseWheelEvent

マウスホイールをスクロールした時

hou.UIEventDevice.mouseWheelは、スクロールの方向に応じて-1または1を返します。

マウスホイールの制御を参照してください。

onKeyEvent

キーイベント用

詳細は、キーボードデバイスを読み込む方法を参照してください。

onKeyTransitEvent

キー遷移イベント用

詳細は、キーボードデバイスを読み込む方法を参照してください。

onMenuAction

コンテキストメニューを選択した時

コンテキストメニューの制御を参照してください。

onMenuPreOpen

メニューステートを更新した時

コンテキストメニューの更新を参照してください。

ハンドラーkwargs引数には、以下の項目が含まれています:

onParmChangeEvent

ステートパラメータイベント

ステートパラメータの制御を参照してください。

onNodeChangeEvent

ステートのノード上でアクションまたは変更が発生した時

hou.OpNode.addEventCallbackと同様の辞書引数を受け取ります。この辞書には特に以下の内容が含まれています:

node

parm_tuple

ノードパラメータ(あれば)を含んだhou.ParmTupleオブジェクト

このハンドラーは、hou.ViewerStateTemplate.bindNodeChangeEventhou.ViewerStateTemplate.bindNodeParmChangeEventで必須です。

onPlaybackChangeEvent

プレイバー変更イベントが発生した時

hou.Playbar.addEventCallbackと同様のイベント情報を含んだ辞書引数を受け取ります:

frame

イベントが実行された時のフレームが指定された浮動小数点

reason

event_typehou.playbarEvent.ChannelListChangedの時にのみ存在するhou.channelListChangedReason。 これは次のどれかになります:

  • リストが完全に置換されている場合はhou.channelListChangedReason.Replaced。または

  • リストがフィルタリングされているだけの場合はhou.channelListChangedReason.Filtered

onPlaybackChangeEventは、hou.ViewerStateTemplate.bindPlaybackChangeEventで必須です。

onCommand

汎用コマンドイベント

hou.SceneViewer.runStateCommandを呼び出すことでコールされます。ハンドラーkwargs引数には、以下の項目が含まれています:

command

コマンド文字列識別子。

command_args

コマンド固有の引数を保持したpythonオブジェクト。

補足メモ:

  • onCommandは、ステートに固有のアクションを実装する際に使用します。例えば、onCommandを使って、ステートパラメータを設定したり、独自の通知の仕組みを実装することができます。詳細は、hou.SceneViewer.runStateCommandを参照してください。

ハンドルのイベントハンドラー

ステートに動的ハンドルをバインドしていると、Houdiniは以下のメソッドをコールします。詳細は、Pythonステートハンドルを参照してください。

メソッド名

コールされるタイミング

メモ

onHandleToState

ユーザがハンドルを使って操作した時

これは、ハンドルを動かしてノードパラメータ(またはステート/ディスプレイ)を更新することができます。

このハンドラーに渡される辞書には、以下の追加項目が含まれています:

handle

ハンドルの文字列ID。

parms

新しいハンドルパラメータ値を含んだ辞書。

mod_parms

変更されたパラメータ名のリスト。

prev_parms

以前のハンドルパラメータ値を含んだ辞書。 これは、差分を計算するのに役立ちます。

ui_event

ドラッグの開始またはドラッグの停止といったハンドル状態に関する情報を含んだhou.UIEventオブジェクト。

onStateToHandle

ノードパラメータが変更された時

これは、ノードパラメータを変更した際にハンドルパラメータを更新することができます。

このハンドラーに渡される辞書には、以下の追加項目が含まれています:

handle

ハンドルの文字列ID。

parms

新しいノードパラメータ値を含んだ辞書。

onBeginHandleToState

ハンドルを使ってユーザが操作を開始した時

これによって、ユーザがハンドルの操作を開始したことが分かります。

このメソッドに渡される辞書には、以下の追加項目が含まれています:

handle

ハンドルの文字列ID。

ui_event

ドラッグを開始または停止といったハンドル状態を持ったhou.UIEventオブジェクト。

onEndHandleToState

ハンドルを使ってユーザが操作を終了した時

これによって、ユーザがハンドルの操作を終了したことが分かります。

このメソッドに渡される辞書には、以下の追加項目が含まれています:

handle

ハンドルの文字列ID。

ui_event

ドラッグの開始または停止といったハンドル情報を持ったhou.UIEventオブジェクト。

選択のイベントハンドラー

ステートにセレクターをバインドしていると、Houdiniは以下のメソッドをコールします。詳細は、選択の制御を参照してください。

メソッド名

コールされるタイミング

メモ

onStartSelection

ユーザが選択を開始した時

このハンドラーに渡される辞書には、以下の項目が含まれています:

name

現在のアクティブセレクターの名前。

drawable_mask

選択可能なDrawableを表現したDrawable名のリスト。 アクティブセレクターがDrawableセレクターでない場合は、このエントリーは利用不可です。

onSelection

ユーザがジオメトリを選択した時

このハンドラーに渡される辞書には、以下の追加項目が含まれています:

selection

完了した選択を表現したhou.GeometrySelectionオブジェクト。 アクティブセレクターがDrawableセレクターの場合、このエントリーは存在しない可能性があります。

drawable_selection

アクティブセレクターがDrawableセレクターの場合、このエントリーには選択されたDrawableコンポーネントを表現したPython辞書が格納されます。 アクティブセレクターがDrawableセレクターでない場合は、このエントリーは利用不可です。

name

現在のアクティブセレクターの名前。

現行選択を“受け入れて”セレクターを終了するようにステートに命令を送るには、 このメソッドからTrue を返さなければなりません。 このメソッドがそれ以外の値を返す場合(または、returnステートメントを含んでいない場合)、セレクターはアクティブなままになります。

onStopSelection

ユーザが選択を終了した時

onSelection()Trueを返したり、選択を受け入れる前にステートを終了させることによって、そのステートが選択を“受け入れた”時にコールされます。

このハンドラーに渡される辞書には、以下の項目が含まれています:

name

現在のアクティブセレクターの名前。

onLocateSelection

ユーザがDrawableセレクターをDrawableジオメトリ上に置いた時

このハンドラーは、DrawableセレクターをDrawableジオメトリ上に置いた時にコールされます。 このハンドラーに渡されるkwargs辞書には、以下の追加項目が含まれています:

drawable_selection

セレクター下のDrawableコンポーネントを表現したPython辞書オブジェクト。

name

現行アクティブセレクターの名前。

ドラッグアンドドロップのイベントハンドラー

Houdiniは、Pythonステートがアクティブな時にビューア内で発生したdrag dropイベントを制御できるように以下のメソッドをコールします。 詳細は、ドラッグアンドドロップの制御を参照してください。

メソッド名

メモ

onDragTest

ユーザがdragイベントを開始した時にコールされます。

onDropGetOptions

ユーザ側で選択可能なdropオプションのリストを構築する時にコールされます。

onDropAccept

選択したdropオプションを制御します。

描画のイベントハンドラー

これらのメソッドは、描画イベントが発生した時にコールされます。 例えば、これらのメソッドは、hou.AdvancedDrawableオブジェクトを処理する時に実行されます。

メソッド名

コールされるタイミング

メモ

onDraw

描画イベント

このメソッドは以下のタイミングでコールされます:

  • ユーザがマウスを動かしたり、マウスクリックしたといったインタラクティブなイベントを引き起こした時。

  • Houdiniが現行ビューポートの再描画を強いられた時。

onDrawInterrupt

描画イベント

このメソッドは以下のタイミングでコールされます:

  • ユーザがビューを回転させたり、タイムラインをスクラブさせたといったようにステートを中断した時。

  • マウスポインタがビューアから出た時。

  • ウィンドウがフォーカスを失なった時。

補足メモ:

  • onDrawは、高度なDrawableで必須になります。kwargs["draw_handle"]が返したハンドルは、レンダーオペレーションを実行するためには必ずDrawableに渡さなければなりません。

    def onDraw(self, kwargs):
        handle = kwargs['draw_handle']
    
        params = { 'translate': hou.Vector3(self.mouse_pos[0],self.mouse_pos[1],self.mouse_pos[2]) }
        self.cursor.render(handle, params )
    
        params = { 
            'translate': hou.Vector3(self.translate[0],self.translate[1],self.translate[2]),
            'color1': hou.Color(self.rgba[0],self.rgba[1],self.rgba[2],self.rgba[3]),
            'blur_width': self.blur_width
        }
        self.face_drawable.render(handle, params)
    
  • onDrawInterruptは任意で、ステートが中断している間に描画が必要になった時にのみ高度なDrawableで使用します。

Viewerステートの検査

Houdiniは、Viewer State Browserウィンドウを使って、登録されているすべてのViewerステートを表示することができます。 このブラウザは、Python Panelメニューを介して開くことができます。

デバッグに関するTips

Houdiniは、Python Viewerステートのデバッグに対応しています。 最も役立つのは、Viewer State Browserです。 ここには、そのブラウザコンソールウィンドウ内にデバッグ情報を表示するための統合ツールが備わっています。 Pythonのprint関数といった基本的な方法を使うこともありですが、このブラウザには、デバッグ関連の機能がたくさん用意されています。

Pythonステートの実行をトレーシング(追跡)する

トレーシングは、Pythonステートの実行中にブラウザコンソールウィンドウ内にデバッグ情報をプリントできるようにする組み込みツールです。 トレーシングはブラウザツールバーから有効にすることができ、File|DebugメニューのTrace Optionsダイアログを使用して設定することができます。 そのTrace Optionsダイアログでは、トレースしたいPythonステートハンドラーとそのハンドラーが実行された時にプリントしたいデータを選択することができます。

  • ビューアステートブラウザのトレーシング機能は、Pythonステートに一行コードを追加することなくPythonステートをデバッグするのに役立ちます。

logメソッド

logメソッドは、Pythonステートクラスで利用可能で、Pythonのprint関数と同様の機能を持っています。 logメソッドは、標準出力にプリントするのではなくて、Viewer State Browserコンソールにメッセージをプリントします。 logメソッドのseverity(重要度)引数を使用することで、異なる背景カラーとテキストカラーでメッセージをプリントすることができます。

  • Viewer State Browserでは、Debug Logボタンを切り替えることで、メッセージをログに記録するタイミングを制御することができます。 これによって、デバッグが必要でない時にコメントアウトしなくても、あなたのコード内にlogメソッドコールを保持することができます。

  • Houdiniは、オブジェクトが作成された後にPythonステートに動的にlogメソッドを追加します。 そのため、__init__がコールされた時点では、まだlogは利用できません。 Viewer State Browserコンソールのメッセージをログに記録するための回避策は、__init__からviewerstate.utils.logを使用することです。

import traceback

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        # マウス位置をログに記録します。
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        self.log("Mouse position x=", device.mouseX(), "y=", device.mouseY(), 
            severity=hou.severityType.ImportantMessage)

        # 現在のPythonコールスタックをログに記録します。
        self.log(''.join(traceback.format_stack()))

ブラウザコンソールはQtベースであり、リッチテキストフォーマットに対応しています。 HTLMスタイルのタグを使用すると、self.logは特定のフォント、フォントサイズ、カラーでテキストを表示することができます。

msg = "Lorem ipsum dolor sit amet."
self.log("<span style='color:#F0FF00; background:orange; font-size:50px;" \
    "font-family:arial narrow'>{}</span>".format(msg))

# 一部の文字がリッチテキスト用特殊文字として予約されています。
# そのような文字をリッチテキストに解析されずに表示させたい場合は、
# エスケープ形式を使用してそれらの文字を無効にしてください。
msg = "<< Lorem ipsum dolor sit amet. >>"
msg = msg.replace("<", "&lt;")
msg = msg.replace(">", "&gt;")
self.log("<font color='#FF00FF'><b><i><u>{}<u/><i/></b><font/>".format(msg))

DebugAid

ユーティリティクラスのviewerstate.utils.DebugAidには、Pythonステートのデバッグサポートが用意されています。 このユーティリティは、self.logなどのログを出す仕組みやViewerステートブラウザで現在利用可能なすべてのデバッグ機能をメソッドとして用意しています:

  • アクティブなViewerステートを検査します。

  • マーカーを追加します。

  • デバッグトレースを有効化します。

  • ロギングコンソールを有効化します。

  • 実行中のViewerステートをリロードします。

PythonハンドルのDebugAidも参照してください。

import traceback
from viewerstate.utils import DebugAid

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer
        self.dbg = DebugAid(self)

    def onEnter(self, kwargs):
        # ステートを検査します。
        self.dbg.marker()
        # このViewerステートを検査します。
        self.dbg.inspect()
        # トレースを開始します。
        self.dbg.trace()

    def onMouseEvent(self, kwargs):
        # マウス位置をログに出します。
        ui_event = kwargs["ui_event"]
        device = ui_event.device()

        self.dbg.marker()
        self.dbg.log("Mouse position x=", device.mouseX(), "y=", device.mouseY())

        # 現在のPythonコールスタックをログに出します。
        self.dbg.log(''.join(traceback.format_stack()))

Pythonのprint関数

最も基本的でありながら依然として役立つデバッグ形式は情報のプリントで、スクリプトが実行された時に変数の内容などをプリントすることです。

  • printを使用する主な利点は、複数行のテキストブロック(例えば、現在のPythonコールスタック)を含むたくさんの情報を出力し、後でスクロールさせて読み返すことができることです。

  • プリント出力を追加するには、Pythonステートコードを編集する必要があります。

この出力は、Houdiniの起動方法やウィンドウの開き方に応じて、コンソールウィンドウ、HoudiniのPythonシェルウィンドウ、Houdiniを起動させた時のシェルに表示させることができます。

Tip

printをステートメントではなく関数として使用することに慣れてしまっているのあれば、from __future__ import printを使用すると良いでしょう。 この関数は、使いやすくて多機能です。

from __future__ import print
import traceback

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        # マウス位置をプリントします。
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        print("Mouse position x=", device.mouseX(), "y=", device.mouseY())

        # 現在のPythonコールスタックをプリントします。
        traceback.print_stack()

hou.SceneViewer.setPromptMessage

hou.SceneViewer.setPromptMessagehou.SceneViewer.clearPromptMessageの関数は、メインのHoudiniウィンドウの下部のスタータスラインにユーザ操作を促すことができます。これらの関数を“応用”することで、デバッグ情報を表示させることができます。

  • このメソッドのメリットは、Houdiniを操作する際にウィンドウの正面中心に情報を表示できることです。

  • このデメリットは、ステータスラインは一度に一行でしか情報を表示できないので、変更があると前のメッセージが消えてしまうことです。

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        # ステータスラインにマウス位置を表示します。
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        message = "Mouse x=%d y=%d" % (device.mouseX(), device.mouseY())
        self.scene_viewer.setPromptMessage(message)

hou.ui.displayMessage

hou.ui.displayMessage関数は、メッセージウィンドウをポップアップして、ユーザがOKかCancelをクリックするのを待ちます。

  • これは、デバッグするのにいくつかメリットがあります。その1つは、待機中にスクリプトが一時停止されるので、非常に簡単に変更内容を調べることができます。また、クリックされたボタンに応じていくつかのフィードバックをスクリプトに渡すことができます。もう1つは、この関数はキーワード引数を持っているので、デフォルトで非表示になっている“詳細”ブロックを追加して展開することができます。これは、例えば現在のPythonコールスタックを表示するのに役立ちます。

  • おそらくループでこの関数を使用したくないことでしょう。というのも、複数のメッセージウィンドウを閉じるのに一々クリックしなければならないので面倒なことになります。

import traceback

import hou

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        # クリックするとメッセージのみが表示されます。
        if device.isLeftButton():
            message = "Mouse x=%d y=%d" % (device.mouseX(), device.mouseY())

            # スクリプト内のこの時点でのコールスタックを取得し、それをメッセージの"詳細"として追加できるように文字列の書式に変換します。
            details = "".join(traceback.format_stack())

            # メッセージを表示して、ユーザがメッセージウィンドウのボタンをクリックするのを待ちます。
            clicked = hou.ui.displayMessage(
                message, buttons=("OK", "Error"),
                details_label="Current call stack",
                details=details
            )

            # ユーザが"Error"ボタンをクリックすると、エラーを引き起こします。
            if clicked == 1:
                raise Exception("Don't blame me!")

ステートのリロード

ディスク上に定義されたステートに関しては、そのディスク上のファイルを変更したら、hou.ui.reloadViewerStateを使って、Houdiniにそのステートをリロードさせることができます。 HDAに埋め込まれたステートをリロードするには、Viewer State Editorを使用します。

デバッグコンテキストメニュー

オプションのviewerstate.utils.createDebugMenuユーテリティを使用することで、デバッグメニューをViewerステートにバインドすることができます。 このデバッグメニューでは、アクティブViewerステートを検査してデバッグメッセージのログをブラウザコンソールに残すといったViewer State Browser機能にアクセスすることができます。

デバッグコンテキストメニューには以下の項目が含まれています:

  • Logging

    コンソールへのログの吐き出しを有効または無効にします。 有効にすると、self.logに残されたメッセージがコンソールに送信されます。

  • Clear

    コンソールのログをクリアします。

  • Inspect

    アクティブViewerステートオブジェクトの内容をコンソールに吐き出します。

  • Trace

    ハンドラーのトレースを有効または無効にします。 有効にすると、アクティブViewerステートハンドラーのトレースのログがコンソールに吐き出されます。

  • Marker

    メッセージを区別するためにコンソール内にマーカーを追加します。

Note

デバッグメニューは、Viewer State Browserのインスタンスがアクティブな時にのみ使用することができます。 そうでない場合は、デバッグメニューを開いても無効になります。

以下は、デバッグメニューをViewerステートに統合する方法を説明するためにViewer State Code Generatorで生成されたサンプルコードです。

import hou
import viewerstate.utils as su

class State(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer


    def onMenuAction(self, kwargs):
        """バインドされたメニューのアクションを実行したコールバック。
        メニュー項目が選択されるとコールされます。
        """

        menu_item = kwargs["menu_item"]
        state_parms = kwargs["state_parms"]

        # viewerstate.utils.onDebugMenuActionは、デバッグメニューのアクションに反応するのに必須です。
        su.onDebugMenuAction(kwargs)

    def onMenuPreOpen(self, kwargs):
        """メニュー内容を描画する前に、そのメニュー内容を更新するためにコールバックを以下に実装します。"""
        menu_states = kwargs["menu_states"]
        menu_item_states = kwargs["menu_item_states"]

        # viewerstate.utils.updateDebugMenuは、Viewer State Browserが非アクティブな場合にデバッグメニューを無効にします。
        su.updateDebugMenu(kwargs)


def createViewerStateTemplate():
    """登録するViewerステートテンプレートを作成して返すために必須となるエントリーポイント。"""

    state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
    state_label = "Empty selection"
    state_cat = hou.sopNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon(kwargs["type"].icon())

    # ステートをデバッグメニューにバインドします。
    template.bindMenu(su.createDebugMenu(state_typename, state_cat))

    return template

生成されるコードには、サブメニューとしてデバッグメニューが追加されます。 このデバッグメニューを平坦なメニューとして追加したいのであれば、以下のようにcreateViewerStateTemplate内のコードを修正します:

menu = hou.ViewerStateMenu(handle_type + "_menu", state_label)
...

# これを ...
menu.addMenu(su.createDebugMenu(state_typename, state_cat))

# ... このコードに置換します。
su.addDebugEntriesToMenu(state_typename, state_cat, menu)

...

サンプル

Pythonステートのサンプルシーンは、viewer_state_demoパッケージと一緒に$HFS/packages/viewer_state_demo/scenes下に配布されています。 このシーンを使用する前に、Viewer State BrowserFile|Load Examples メニューをクリックしてそのパッケージをHoudiniに読み込んでください。

Demo Viewer State シェルフツールまたはメインメニューバー内の File|Open… メニューからサンプルシーンを読み込むことができます。

HOM API

Pythonスクリプト

はじめよう

次のステップ

リファレンス

  • hou

    Houdiniにアクセスできるサブモジュール、クラス、ファンクションを含んだモジュール。

導師レベル

Python Viewerステート

Pythonビューアハンドル

プラグインタイプ