Houdini 19.0 Pythonスクリプト

Pythonハンドル イベント

Viewer Handleイベントをリッスン(受信)してそれに反応する方法。

On this page

Pythonビューアハンドル

概要

(Pythonハンドルの実装方法の基本はViewerハンドルを参照してください。)

ノードパラメータのインタラクティブな変更を適切に対応させるには、Pythonハンドルがマウス移動、マウスクリック、パラメータ変更などの様々なローレベルなイベントを解釈する必要があります。 キーボードイベントやメニューイベントなどの他のUIイベント対応も可能です。詳細は、入力イベントを参照してください。

マウスのハンドリング

Houdiniは、3つの異なるハンドラーを使ってマウスイベントを制御することができます: onMouseEvent,onMouseIndirectEvent, onMouseWheelEvent

onMouseEvent

ハンドラーは、Pythonハンドルのインタラクティブなワークフローのほとんどの実装に使用します。

  • onMouseEventは、マウスがハンドルガジェットの上にある時にイベントとイベントを制御することができます。

  • onMouseEventは、アクティブステートのonMouseEvent前に必ず処理されます。

  • hou.ViewerEvent(kwargs["ui_event"])を使用することで、ローレベルでのマウス操作、マウスクリックの検知などを追跡することができます。

  • hou.ViewerHandleContext(self.handle_context)を使用することで、ガジェットのステートにアクセスすることができます。

  • onMouseEventをコールする前に、Houdiniは、マウス下にどのジオメトリが存在するのか検索するパスを実行し、そのジオメトリに関連付けられている(アクティブな)ガジェットを使ってself.handle_contextを更新します。自作でそのガジェットを検索するためのジオメトリ交差を実装する必要はありません。

  • onMouseEvent内でself.handle_contextからアクティブガジェットにアクセスすることで、実行したいアクションを選択することができます。

  • hou.ViewerHandleDraggerを使用することで、マウス移動を軸、平面、コンストラクション平面に沿うように拘束するPythonハンドルアクションを実装することができます。

ハンドルドラッガーとハンドルガジェットを使ってハンドルの移動パラメータを変更する方法を説明したサンプルを以下に載せます。

import viewerhandle.utils as hu

def createViewerHandleTemplate():
    handle_type = 'handle_example'
    handle_label = 'Handle example'
    handle_cat = [hou.sopNodeTypeCategory()]

    template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat)
    template.bindFactory(Handle)

    # ハンドルパラメータを定義します。
    template.bindParameter( hou.parmTemplateType.Float, name="tx", label="Tx", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="ty", label="Ty", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="tz", label="Tz", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )

    # すべてのパラメータをエクスポートします。
    template.exportParameters(["tx", "ty", "tz"])

    # ピボットガジェットをバインドします。
    template.bindGadget( hou.drawableGeometryType.Face, "pivot" )

    return template    

class Handle(base_class.Handle):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

        # ハンドルを移動させるドラッガーを作成します。
        self.translate_dragger = hou.ViewerHandleDragger("translate_dragger")

        # ハンドルのトランスフォーム操作をサポートしたユーティリティクラス。
        self.xform_aid = hu.TransformAid(self, parm_names={"translate":["tx","ty","tz"]})

def onMouseEvent( self, kwargs ):
    """ ガジェットをピックしてドラッグした時にコールされます。
    """

    # ロケートまたはピックされているアクティブガジェットを取得します。
    if self.handle_context.gadget() == "pivot":

        ui_event = kwargs["ui_event"]
        reason = ui_event.reason()

        # ピボットガジェットは、ユーザがドラッグして移動パラメータを変更するためのガジェットです。
        if reason == hou.uiEventReason.Start:
            # ハンドルのtx,ty,tzパラメータをhou.Vector3オブジェクトとして取得します。
            handle_pos = self.xform_aid.parm3("translate")

            # hou.ViewerHandleDraggerオブジェクトを使ってハンドルの移動を開始します。
            # このドラッガーには、直線沿い、または、平面沿いにドラッグといった特別な操作をするための様々なメソッドが用意されています。
            # ここでは、単にピボットをワールド空間で移動させます。
            self.translate_dragger.startDrag(ui_event, handle_pos)

        elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]:
            # ピボットを途切れることなくドラッグします。
            drag_values = self.translate_dragger.drag(ui_event)

            # ドラッガーが返した位置を使ってハンドルのtx,ty,tzパラメータを更新します。
            self.xform_aid.addToParm3("translate", drag_values["delta_position"])

            # 注: このトランスフォームはonDrawSetup内で計算されます。

            if reason == hou.uiEventReason.Changed:
                # 終了してドラッグを抜けます。
                self.translate_dragger.endDrag()

        # イベントを消費します。
        return True

    return False

この実装パターンは、非常に汎用的で、たいていのPythonハンドルで再利用することができます。 onMouseEventはガジェットがピックされた時に常にコールされるので、まず最初にマウスが押されているかどうかをチェックする必要がありません。 代わりに、どのガジェットがactiveになっているのかチェックして、それに対して作用させます。

hou.uiEventReason.Startは、マウスが押されたことを知らせるので、この時点でのドラッガーは、そのハンドルのtranslate位置(xyz parameters)startDragをコールしてセットアップする必要があります。

UIイベントがhou.uiEventReason.Changedまたはhou.uiEventReason.Activeに設定された時にdragメソッドをコールし、 ドラッガーが返した差分値でハンドルのtranslateパラメータを更新します。 マウスが離されると(hou.uiEventReason.Changed)、endDragをコールしてドラッガーを終了します。

おそらくself.handle_context__init__関数内で明示的に作成されていないことにお気づきかと思います。 これはHoudiniによって作成されるクラスアトリビュートなので、あなた自身で作成する必要はありません。 self.handle_contextは、ピックとロケートに関するガジェットの状態を格納したhou.ViewerHandleContextオブジェクトで設定されます。 このオブジェクトは、最新コンテキスト情報で常に更新されるようにHoudini側で管理されます。

onMouseIndirectEvent

This handler is used for handling events if the mouse is not located over one of the python handle gadgets.

  • onMouseIndirectEvent is called for the handle closest to the mouse.

  • Use hou.ViewerEvent (kwargs["ui_event"]) to track down low-level mouse interactions.

  • Houdini will only call onMouseIndirectEvent for the following events: hou.uiEventReason.Start, hou.uiEventReason.Active, hou.uiEventReason.Changed.

  • Dragging the mouse with or will not trigger the handler.

  • onMouseIndirectEvent must return True when the event has been consumed.

This example demonstrates how to translate the handle by dragging the mouse with .

import viewerhandle.utils as hu

def createViewerHandleTemplate():
    handle_type = 'handle_example'
    handle_label = 'Handle example'
    handle_cat = [hou.sopNodeTypeCategory()]

    template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat)
    template.bindFactory(Handle)

    # Define the handle parameters
    template.bindParameter( hou.parmTemplateType.Float, name="tx", label="Tx", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="ty", label="Ty", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="tz", label="Tz", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )

    # Export all parameters
    template.exportParameters(["tx", "ty", "tz"])

    # Bind the pivot gadget
    template.bindGadget( hou.drawableGeometryType.Face, "pivot" )

    return template    

class Handle(base_class.Handle):
    PIVOT_SIZE = 0.3

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

        # Creates a dragger for translating the handle
        self.translate_dragger = hou.ViewerHandleDragger("translate_dragger")

        # Utility class to support the handle transform operations
        self.xform_aid = hu.TransformAid(self, parm_names={"translate":["tx","ty","tz"]})

        # Pivot gadget
        sops = hou.sopNodeTypeCategory()
        verb = sops.nodeVerb("box")
        psize = Handle.PIVOT_SIZE
        verb.setParms(
            {   "type" : 1,
                "size" : (psize,psize,psize),
                "divrate": (2,2,2)
            })
        pivot = hou.Geometry()
        verb.execute(pivot, [])
        self.pivot = self.handle_gadgets["pivot"]
        self.pivot.setGeometry(pivot)
        self.pivot.show(True)

def onMouseInteractEvent(self, kwargs):
    """ Called when the mouse is dragged with MMB.
    """

    ui_event = kwargs["ui_event"]
    reason = ui_event.reason()

    consumed = False        

    # The example moves the pivot when MMB is down. 
    #
    # The implementation is not that different from `onMouseEvent`. 
    #
    # We don't need to check for the active gadget as the mouse cannot 
    # be over a gadget when onMouseInteractEvent is called.
    #
    # Using a "constraint free" dragger will compute the handle position 
    # relative to the world space position under the mouse.

    if reason == hou.uiEventReason.Start:
        # Get the handle tx,ty,tz parameters as a hou.Vector3 object
        handle_pos = self.xform_aid.parm3("translate")

        # init the dragger
        self.translate_dragger.startDrag(ui_event, handle_pos)

        consumed = True

    elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]:
        # drag the pivot continously
        drag_values = self.translate_dragger.drag(ui_event)

        # Update the handle tx, ty, tz parameters with the position returned by the dragger.
        self.xform_aid.addToParm3("translate", drag_values["delta_position"])

        # Note: The transform will be computed in onDrawSetup.

        if reason == hou.uiEventReason.Changed:
            # We are done, exit the drag.
            self.translate_dragger.endDrag()

        consumed = True

    return consumed

def onMouseEvent( self, kwargs ):
    """ Called when a gadget is picked and dragged.
    """

    consumed = False        

    # Get the active gadget being located or picked.
    if self.handle_context.gadget() == "pivot":

        ui_event = kwargs["ui_event"]
        reason = ui_event.reason()

        # The pivot gadget is what the user drags to change the translate parameters.
        if reason == hou.uiEventReason.Start:
            # Get the the handle tx,ty,tz parameters as a hou.Vector3 object
            handle_pos = self.xform_aid.parm3("translate")

            # Start the handle translate with a hou.ViewerHandleDragger object. 
            # The dragger has various methods for specialized operations such as 
            # dragging along a line or along a plane. Here we just move the pivot 
            # in world space.
            self.translate_dragger.startDrag(ui_event, handle_pos)

            consumed = True

        elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]:
            # drag the pivot continously
            drag_values = self.translate_dragger.drag(ui_event)

            # Update the handle tx, ty, tz parameters with the position returned by the dragger.
            self.xform_aid.addToParm3("translate", drag_values["delta_position"])

            # Note: The transform will be computed in onDrawSetup.

            if reason == hou.uiEventReason.Changed:
                # We are done, exit the drag.
                self.translate_dragger.endDrag()                   

            consumed = True

    return consumed

def onDraw( self, kwargs ):
    self.pivot.draw(kwargs["draw_handle"])

onMouseWheelEvent

マウスホイールの反応は、onMouseWheelEventによって実現されます。 このハンドラーは、PythonステートのonMouseWheelEventハンドラーとほぼ同じように動作します。

しかし、これらの2つのハンドラーには重大な違いがあります。 PythonハンドルのonMouseWheelEventハンドラーは、ロケート(マウス下の検索)されたガジェットでのみコールされるので、 マウスホイールイベントを発動するには、マウスカーソルがガジェット上に必ずなければなりません。 場合によっては、ロケートされたガジェット上にマウスを置き続けるのは面倒になるので、専用ガジェットまたは固定ガジェットのどれかでマウスホイールイベントを有効にして、 状況に応じてPythonハンドルを設計すると良いでしょう。

他にも、HoudiniはPythonステートのonMouseWheelEventハンドラーの前に常にPythonハンドルのonMouseWheelEventをコールします。 Pythonステートのハンドラーを間違って処理しないように必ずPythonハンドルのonMouseWheelEventがこのイベントを消費するようにしてください。

パラメータ

パラメータは、Pythonハンドルの重要な部分です。 パラメータを使用しなかった場合、ビューポート内でノードパラメータを変更する際にPythonハンドルを使用することができません。

ハンドルパラメータの定義は、登録処理の一部であり、hou.ViewerHandleTemplate.bindParameter()を使用することで、Pythonハンドルテンプレートにパラメータを追加することができます。 定義されたすべてのパラメータは、適切にエクスポートされた場合にのみインタラクティブにビューポート内で制御することができます。 パラメータは、hou.ViewerHandleTemplate.exportParameters()を使用してエクスポートします。 このステップでは、PythonステートがPythonハンドルパラメータをノードパラメータにバインドすることができます。 特定のパラメータをエクスポートしないように選択した場合、そのパラメータは外部で利用できなくなります。

Pythonハンドルパラメータだけでなく、hou.ViewerHandleTemplate.bindSetting()を使用して 設定 パラメータもPythonハンドルに追加することができます。 これらの設定パラメータは、典型的にはPythonハンドルの構成設定として使用します。 例えば、ガジェット用に異なるカラー値やスケール値などを格納する設定パラメータを作成すると良いでしょう。 Pythonハンドルのパラメータと設定は、Handle Parameter DialogやPythonハンドルのhandle_parmsクラスアトリビュートから変更することができます。 ただし、設定パラメータはエクスポート不可なので、ビューポートからインタラクティブに変更することはできません。

Note

パラメータ値と設定値はシーンに保存され、そのシーンを読み直すと最後に保存された値に戻ります。

パラメータ変更に反応させる方法

onParmChangeEventハンドラーを使用することで、パラメータ変更に反応させることができます。 Houdiniは、Pythonハンドルのパラメータまたは設定パラメータが変更された後でそのハンドラーをコールします。 詳細は、onParmChangeEventを参照してください。

以下のサンプルでは、Pythonハンドルのパラメータを定義してパラメータ変更に反応させる方法を説明しています。

import viewerhandle.utils as hu

def createViewerHandleTemplate():
    handle_type = 'handle_example'
    handle_label = 'Handle example'
    handle_cat = [hou.sopNodeTypeCategory()]

    template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat)
    template.bindFactory(Handle)

    # ハンドルパラメータは、典型的にはガジェットとそれにバインドさえたノードパラメータを制御するのに使用します。
    template.bindParameter( hou.parmTemplateType.Float, name="tx", label="Tx", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="ty", label="Ty", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="tz", label="Tz", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )

    # ... また、ハンドルパラメータはエクスポートする必要があるので、Houdiniに使用させたいハンドルパラメータのみをエクスポートします。
    # エクスポートしたパラメータは、Pythonステート内でノードパラメータをバインド(例えば、hou.ViewerStateTemplate.bindHandleStatic)するのに使用することができます。
    template.exportParameters(["tx", "ty", "tz"])

    # Settingは、ハンドルの挙動を制御するのに使用されるパラメータです。
    template.bindSetting( hou.parmTemplateType.Toggle, name="face", label="Face", default_value=True, align=True )
    template.bindSetting( hou.parmTemplateType.Toggle, name="wire", label="Wire", default_value=True, align=True )
    template.bindSetting( hou.parmTemplateType.Toggle, name="pivot", label="Pivot", default_value=True, align=True )
    template.bindSetting( hou.parmTemplateType.Toggle, name="knob", label="Knob", default_value=True, align=False )

    # ガジェットをバインドします。
    template.bindGadget( hou.drawableGeometryType.Face, "pivot" )
    template.bindGadget( hou.drawableGeometryType.Face, "face" )
    template.bindGadget( hou.drawableGeometryType.Line, "wire" )
    template.bindGadget( hou.drawableGeometryType.Point, "knob" )

    return template

class Handle(base_class.Handle):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
        self.xform_aid = hu.TransformAid(self, parm_names={"translate":["tx","ty","tz"]})

        # 簡潔にするためにガジェットの初期化はスキップしました。

    def onParmChangeEvent(self, kwargs):
        """
        ハンドルのパラメータまたは設定が変更された時にコールされます。
        """
        parm_name = kwargs['parm_name']
        parm_value = kwargs['parm_value']

        if parm_name in ["tx","ty","tz"]:
            # ハンドルトランスフォームを新しい値で更新します。
            self.xform_aid.updateTransform()
        elif parm_name == "face":
            kwargs["handle_gadgets"]["face"].show(parm_value)
        elif parm_name == "wire":
            kwargs["handle_gadgets"]["wire"].show(parm_value)
        elif parm_name == "pivot":
            kwargs["handle_gadgets"]["pivot"].show(parm_value)
        elif parm_name == "knob":
            kwargs["handle_gadgets"]["knob"].show(parm_value)

        # ビューポートを更新します。
        self.scene_viewer.curViewport().draw()

パラメータ値へのアクセス

Python handle’s parameters and settings can be accessed from the handle_parms dictionary. The dictionary is accessible from any Python handle handlers.

def createViewerHandleTemplate():
    handle_type = 'handle_example'
    handle_label = 'Handle example'
    handle_cat = [hou.sopNodeTypeCategory()]

    template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat)
    template.bindFactory(Handle)

    # Handle parameters.
    template.bindParameter( hou.parmTemplateType.Float, name="tx", label="Tx", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="ty", label="Ty", 
        min_limit=-10.0, max_limit=10.0, default_value=1.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="tz", label="Tz", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="float3"", label="Float 3", 
        min_limit=-10.0, max_limit=10.0, default_value=[1.0,1.0,1.0], num_components=3 )

    # Handle settings.
    template.bindSetting( hou.parmTemplateType.Toggle, name="ui_guides", label="Draw UI guides", default_value=True)

    return template    

class Handle(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def onActivate(self, kwargs):
        self.log("Handle tx parm value", kwargs["handle_parms"]["tx"]["value"])
        self.log("Handle ty parm value", kwargs["handle_parms"]["ty"]["value"])
        self.log("Handle tz parm value", kwargs["handle_parms"]["tz"]["value"])
        self.log("Handle float3 parm value", kwargs["handle_parms"]["float3"]["value"])
        self.log("UI guides value", kwargs["handle_parms"]["ui_guides"]["value"])

# output
'Handle tx parm value' 0.0 
'Handle ty parm value' 1.0 
'Handle tz parm value' 0.0 
'Handle float3 parm value' [1.0, 1.0, 1.0] 
'UI guides value' 1 

描画

Pythonハンドルは、最大2個の描画ハンドラーを使用します:

  • onDraw:

    Pythonハンドルガジェットと他のDrawablesを描画する際に使用します。 これは実装するのが非常に簡単で、基本的にハンドルガジェットとDrawablesのdrawメソッドにデリゲート(委任)します。 このハンドラーは、PythonステートのonDrawハンドラーと同様です。

  • onDrawSetup:

    現在のカメラ位置に関係なく固定サイズを維持できるようにするには、Pythonハンドルを動的にスケールさせる必要があります。 onDrawSetupは、典型的にはhou.ViewerHandleContext.scaleFactor()の補助を使ってハンドルスケールを計算する際に使用します。 新しく計算されたスケール値を使用すると、PythonハンドルはDrawableガイドのトランスフォーム行列と一緒に自身のトランスフォーム行列も更新します。

def onDrawSetup(self, kwargs):
    # 現在のハンドル位置を使用してスケール値を計算します。
    hpos = self.xform_aid.parm3("translate")

    # スケール値を計算します。
    fixed_scale_value = 100.0
    scale = self.handle_context.scaleFactor(hpos)*fixed_scale_value

    # 新しいスケールを使ってハンドルのトランスフォーム行列を再構築します。
    xform = self.xform_aid.updateTransform(s=[scale,scale,scale])

    # ガジェットのトランスフォームを更新します。
    kwargs["handle_gadgets"]["face"].setTransform(xform)
    kwargs["handle_gadgets"]["wire"].setTransform(xform)
    kwargs["handle_gadgets"]["pivot"].setTransform(xform)
    kwargs["handle_gadgets"]["knob"].setTransform(xform)

Pythonビューアハンドル

Pythonスクリプト

はじめよう

次のステップ

リファレンス

  • hou

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

導師レベル

Python Viewerステート

Pythonビューアハンドル

プラグインタイプ