Houdini 20.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

このハンドラーは、マウスがどのPythonハンドルガジェット上にも乗っていない場合のイベントの制御に使用します。

  • onMouseIndirectEventは、そのマウスから最も近くにあるハンドルに対してコールされます。

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

  • Houdiniは次のイベントに対してのみonMouseIndirectEventをコールします: hou.uiEventReason.Start, hou.uiEventReason.Active, hou.uiEventReason.Changed

  • またはによるマウスドラッグは、このハンドラーを発動しません。

  • onMouseIndirectEventは、イベントが消費された時にTrueを返さなければなりません。

以下のサンプルでは、によるマウスドラッグでハンドルを移動させる方法について説明しています。

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):
    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")

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

        # ピボットガジェット
        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):
    """ マウスがMMBドラッグされた時にコールされます。
    """

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

    consumed = False        

    # このサンプルは、MMBが押されている時にピボットを動かします。
    #
    # この実装は`onMouseEvent`とさほど違いはありません。
    #
    # onMouseInteractEventがコールされる時はマウスはガジェット上にないわけなので、
    # アクティブガジェットをチェックする必要がありません。
    #
    # "拘束のない"ドラッガーを使用すると、マウス下のワールド空間位置を基準に
    # ハンドル位置が計算されます。

    if reason == hou.uiEventReason.Start:
        # ハンドルのtx,ty,tzパラメータをhou.Vector3オブジェクトとして取得します。
        handle_pos = self.xform_aid.parm3("translate")

        # ドラッガーを初期化します。
        self.translate_dragger.startDrag(ui_event, handle_pos)

        consumed = True

    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()

        consumed = True

    return consumed

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

    consumed = False        

    # ロケートまたはピックされているアクティブガジェットを取得します。
    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)

            consumed = True

        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()

            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_parms辞書からアクセスすることができます。 この辞書は、どのPythonハンドルハンドラーからでもアクセス可能です。

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=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 )

    # ハンドル設定。
    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"])

# 出力
'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ビューアハンドル

プラグインタイプ