Houdini 21.0 Pythonスクリプト

Pythonハンドル ガジェット

Gadget Drawablesを使ってビューポート内に3Dビューアハンドルをレンダリングする方法。

On this page

概要

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

A Gadget is a geometric representation serving as a visual component of a Python handle. Primarily designed for the visuals of 3D Python handles, gadgets can represent interactive elements. For example, in a custom translate handle, a gadget might depict the pivot point for object translation. Similarly, in a slider handle, a gadget would likely be the draggable knob used to modify a parameter. A Python handle typically employs one or more gadgets to render its visual interface.

2D Python handles, in contrast, do not utilize gadget drawables for their visual representation. Instead, the hou.Drawable2D class serves a similar role for locating and picking the handle’s visual components within a 2D context.

Gadget Drawable

hou.GadgetDrawableクラスは、Pythonハンドルのガジェットを表現しています。 Gadget Drawableは基本的には、ユーザがどのオブジェクトを選択しているのか、そのカーソル下のどのオブジェクトが指されているのかをPythonハンドルが判断するのに必須なロケート(マウス下の検索)とピック(選択)の機能性が追加された特殊なGeometry Drawable APIです。

Geometry Drawableと同様に、Houdiniで対応しているどのメッシュジオメトリもGadget Drawableに設定することができ、そのジオメトリにはVerbsを使用したり、他にもSOPネットワークから生成されたジオメトリを使用することができます。 しかし、パフォーマンスの観点では、Pythonハンドルガジェットには、Verbsを介して定義されたジオメトリを割り当てるのが良いです。

Geometry Drawableとは違って、Gadget Drawableは、使用する前にまず最初に必ず登録しなければなりません。 この登録した情報があることで、Houdini自身がPythonハンドルのガジェットインスタンスを生成し、そのガジェットの大元のジオメトリに対してローレベルのピックとロケートの操作を実行することができます。

便宜的に、Houdiniで生成されたガジェットインスタンスは、Pythonハンドルクラスのhandle_gadgetsアトリビュートに保存されます。

Gadget Drawableの使い方

Gadget Drawableの使い方は、Geometry Drawableの使い方と違いはありません。 Houdiniは、ガジェットオブジェクトを自動で生成することで、開発者にとってGadget Drawableを使いやすくしています。 Pythonハンドルの実装では、通常ではジオメトリと一連の適切なパラメータ値でそのガジェットを設定します。

import resourceutils as ru

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.bindIcon('$HFS/houdini/pic/minimizedicon.pic')

    # ガジェットを登録します。
    template.bindGadget( hou.drawableGeometryType.Line, "line" )
    template.bindGadget( hou.drawableGeometryType.Point, "point" )

    return template

class Handle(object):

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

        # Houdiniの標準カラーを使ってガジェットを設定します。
        self.color_options = ru.ColorOptions(self.scene_viewer)

        # "line"ガジェットを設定します。
        sops = hou.sopNodeTypeCategory()
        verb = sops.nodeVerb("box")
        verb.setParms({"type" : 1, "divrate":(2,2,2), "size":(0.9,0.9,0.9)})
        geo = hou.Geometry()
        verb.execute(geo, [])

        # Houdiniは、通常の描画には`draw_color`を、ピックが有効な時のガジェットの描画には`pick_color`を、ガジェットの強調表示には`locate_color`を使用します。
        self.line = self.handle_gadgets["line"]
        self.line.setParams({"draw_color":self.color_options.colorFromName("YaxesColor")})
        self.line.setParams({"pick_color":self.color_options.colorFromName("PickedHandleColor")})
        self.line.setParams({"locate_color":self.color_options.colorFromName("HandleLocateColor", alpha_name="HandleLocateAlpha")})
        self.line.setGeometry(geo)

        # "point"ガジェットを設定します。
        self.point = self.handle_gadgets["point"]
        verb.setParms({"type" : 1, "divrate":(2,2,2)})
        verb.execute(geo, [])

        self.point.setParams({"draw_color":self.color_options.colorFromName("XaxesColor")})
        self.point.setParams({"pick_color":self.color_options.colorFromName("PickedHandleColor")})
        self.point.setParams({"locate_color":self.color_options.colorFromName("HandleLocateColor", alpha_name="HandleLocateAlpha")})
        self.point.setParams({"radius":7.0})
        self.point.setGeometry(geo)

    def onDraw(self, kwargs):
        # 各ガジェットを描画します。
        draw_handle = kwargs["draw_handle"]

        self.line.draw(draw_handle)
        self.point.draw(draw_handle)

__init_関数は、設定ガジェットのカラーにはresourceutils.ColorOptionsアトリビュートを使用します。 ColorOptionsは、Houdiniの標準カラーを取得するユーティリティクラスです。 カラー値の選択にはこのユーティリティを使用して、Pythonハンドルと他のHoudiniビルトインハンドルの整合性を合わせることを強く推奨します。 Colors fetched with resourceutils.ColorOptionsを使って取得されたカラーは、バックグラウンドのテーマが変更された時でもビューポートカラーに追従します。

Tip

ハンドルコンポーネントのロケートに使用される輝きの設定はデフォルトで設定されますが、locate_glowパラメータを使用してカスタマイズすることができます。 詳細は、hou.GadgetDrawableを参照してください。

ガジェットとパラメータのバインド

他のビルトインハンドルと同様に、Handle Bindingsダイアログを使ってインタラクティブに、または、Pythonステートを使ってプログラム的に、ノードパラメータをPythonハンドルにバインドすることができます。 バインドされたパラメータ(s)を視覚的に操作できるようにPythonハンドル実装で使用されたガジェットは、描画、対話操作などで有効になります。

しかし、ユーザがPythonハンドルパラメータの一部のみをバインドし、残りをバインドしないままにした場合はどうなるでしょう? Pythonハンドルの実装内にそれらの“バインドしなかった”パラメータを必要とするガジェットは、それらのパラメータを変更することができないので、ユーザには使い物にならないので、 それらのガジェットがまだビューポートで利用可能だとユーザに決して信じ込ませたくありません。

このような場合にPythonハンドルの実装ですべき事は、単にそれらの“バインドしなかった”ガジェットを非表示にすることです。 Pythonハンドル側でランタイム時にガジェットが利用可能かどうかを知る方法は、必須パラメータのリストをhou.ViewerHandleTemplate.bindGadgetに渡すことです。 この情報があれば、Houdiniは、 すべて の必須パラメータがバインドされている場合にガジェットを利用可能にし、そうでない場合に利用不可にすることができるようになります。

必須ハンドルパラメータのリストを指定してガジェットを登録するのは任意ではありますが、強く推奨します。 そうした方がもっと動的になり、ユーザにとって使いやすくなります。

“バインドを意識した”ガジェットでPythonハンドルの実装を示した高度なサンプルを以下に載せています。

Note

以下のサンプルは少し難解に見えますが、onMouseEventで使用されているコードは繰り返しの連続にすぎないです。 これは、Pythonハンドルの実装の詳細をもっと上手く説明することを意図して処理されています。

import resourceutils as ru

def createViewerHandleTemplate():
    handle_type = kwargs["type"].definition().sections()["ViewerHandleName"].contents()    
    handle_label = 'Handle example'
    handle_cat = [hou.sopNodeTypeCategory()]

    template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat)
    template.bindFactory(Handle)
    template.bindIcon('$HFS/houdini/pic/minimizedicon.pic')

    # ハンドルパラメータを登録します。
    template.bindParameter( hou.parmTemplateType.Float, name="X", default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="Y", default_value=0.0 )

    # 必須ハンドルパラメータのリストを指定してガジェットを登録します。
    template.bindGadget( hou.drawableGeometryType.Line, "x_gadget", parms=["X"] )
    template.bindGadget( hou.drawableGeometryType.Line, "y_gadget", parms=["Y"] )

    # その必須ハンドルパラメータをHoudiniへエクスポートします。
    template.exportParameters(["X","Y"])

    return template

class Handle(object):

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

        self.current_pos = hou.Vector3()
        self.xy_dragger = hou.ViewerHandleDragger("xy_dragger")

        # Houdini標準カラーを使ってガジェットを設定します。
        self.color_options = ru.ColorOptions(self.scene_viewer)

        # ガジェットが利用可能な場合にのみ、そのガジェットを初期化します。
        sop_cat = hou.sopNodeTypeCategory()

        self.x_gadget = self._gadget("x_gadget")
        if self.x_gadget:
            verb = sop_cat.nodeVerb("line")
            verb.setParms(
                {   "origin" : (0,0,0),
                    "dir" : (1,0,0),
                    "dist": 1.0,
                    "points": 2
                })
            geo = hou.Geometry()        
            verb.execute(geo, [])

            self.x_gadget.setParams({"draw_color":self.color_options.colorFromName("XaxesColor")})
            self.x_gadget.setGeometry(geo)

        self.y_gadget = self._gadget("y_gadget")
        if self.y_gadget:
            verb = sop_cat.nodeVerb("line")
            verb.setParms(
                {   "origin" : (0,0,0),
                    "dir" : (0,1,0),
                    "dist": 1.0,
                    "points": 2
                })
            geo = hou.Geometry()        
            verb.execute(geo, [])

            self.y_gadget.setParams({"draw_color":self.color_options.colorFromName("YaxesColor")})
            self.y_gadget.setGeometry(geo)

    def _gadget(self, gadget_name):
        try:
            return self.handle_gadgets[gadget_name]
        except:
            return None

    def _showGadget(self, value):
        if self.x_gadget:
            self.x_gadget.show(value)

        if self.y_gadget:
            self.y_gadget.show(value)

    def onActivate(self, kwargs):
        self._showGadget.show(True)

    def onDeactivate(self, kwargs):
        self._showGadget.show(False)

    def onMouseEvent(self, kwargs):

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

        consumed = False

        # ガジェットが利用不可であれば、current_gadget_nameはNoneに設定されるはずです。
        current_gadget_name = self.handle_context.gadget()
        current_gadget = self._gadget(current_gadget_name)

        if current_gadget_name in ["x_gadget"]:

            # x_gadgetを制御します。
            if reason == hou.uiEventReason.Start:

                # X軸方向に動かします。
                self.xy_dragger.startDragAlongLine(ui_event, self.current_pos, 
                    hou.Vector3(1, 0, 0))
                current_gadget.show(True)

                if self.y_gadget:
                    self.y_gadget.show(False)

            elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]:
                drag_values = self.xy_dragger.drag(ui_event)
                delta_pos = drag_values["delta_position"]

                # ドラッグ中にハンドルパラメータを更新します。
                self.current_pos += delta_pos
                self.handle_parms["X"]["value"] += delta_pos[0]

                # ガジェットのトランスフォームを更新します。
                xform = current_gadget.transform() * hou.hmath.buildTranslate(delta_pos)
                current_gadget.setTransform(xform)

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

                    if self.y_gadget:
                        xform = hou.hmath.buildTranslate(self.current_pos)
                        self.y_gadget.setTransform(xform)
                        self.y_gadget.show(True)

            consumed = True

        elif current_gadget_name in ["y_gadget"]:

            # y_gadgetを制御します。
            if reason == hou.uiEventReason.Start:

                # Y軸方向に動かします。
                self.xy_dragger.startDragAlongLine(ui_event, self.current_pos, 
                    hou.Vector3(0, 1, 0))
                current_gadget.show(True)

                if self.x_gadget:
                    self.x_gadget.show(False)

            elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]:
                drag_values = self.xy_dragger.drag(ui_event)
                delta_pos = drag_values["delta_position"]

                # ドラッグ中にハンドルパラメータを更新します。
                self.current_pos += delta_pos
                self.handle_parms["Y"]["value"] += delta_pos[1]

                # ガジェットのトランスフォームを更新します。
                xform = current_gadget.transform() * hou.hmath.buildTranslate(delta_pos)
                current_gadget.setTransform(xform)

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

                    # x_gadgetラインを更新します。
                    if self.x_gadget:
                        xform = hou.hmath.buildTranslate(self.current_pos)
                        self.x_gadget.setTransform(xform)
                        self.x_gadget.show(True)

            consumed = True

        return consumed

    def onDraw(self, kwargs):
        # 各ガジェットを描画します。
        draw_handle = kwargs["draw_handle"]

        if self.x_gadget:
            self.x_gadget.draw(draw_handle)

        if self.y_gadget:
            self.y_gadget.draw(draw_handle)

以下のサンプルでは、上記のサンプルの実装を簡素化するためにモジュール方式を採用しています。

import resourceutils as ru

def createViewerHandleTemplate():
    handle_type = kwargs["type"].definition().sections()["ViewerHandleName"].contents()    
    handle_label = 'Handle modular example'
    handle_cat = [hou.sopNodeTypeCategory()]

    template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat)
    template.bindFactory(Handle)
    template.bindIcon('$HFS/houdini/pic/minimizedicon.pic')

    # ハンドルパラメータを登録します。
    template.bindParameter( hou.parmTemplateType.Float, name="X", default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="Y", default_value=0.0 )

    # 必須ハンドルパラメータのリストを指定してガジェットを登録します。
    template.bindGadget( hou.drawableGeometryType.Line, "x_gadget", parms=["X"] )
    template.bindGadget( hou.drawableGeometryType.Line, "y_gadget", parms=["Y"] )

    # その必須ハンドルパラメータをHoudiniへエクスポートします。
    template.exportParameters(["X","Y"])

    return template

class Handle(object):

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

        self.xform = hou.Matrix4(1)
        self.gadgets = []

        self.gadgets.append(LineGadget(self, self.scene_viewer, 
            {   "gadget_name": "x_gadget",
                "gadget_dir": hou.Vector3(1,0,0),
                "gadget_color": "XaxesColor",
                "parm_name": "X",
                "parm_index": 0
            })
        )

        self.gadgets.append(LineGadget(self, self.scene_viewer, 
            {   "gadget_name": "y_gadget",
                "gadget_dir": hou.Vector3(0,1,0),
                "gadget_color": "YaxesColor",
                "parm_name": "Y",
                "parm_index": 1
            })
        )

    def onActivate(self, kwargs):
        for gadget in self.gadgets:
            gadget.show(True)

    def onDeactivate(self, kwargs):
        for gadget in self.gadgets:
            gadget.show(False)

    def onDraw(self, kwargs):
        draw_handle = kwargs["draw_handle"]
        for gadget in self.gadgets:
            gadget.onDraw(draw_handle) 

    def onMouseEvent(self, kwargs):

        ui_event = kwargs["ui_event"]
        reason = ui_event.reason()
        current_gadget_name = self.handle_context.gadget()

        for gadget in self.gadgets:
            if reason == hou.uiEventReason.Start:
                gadget.onBeginInteracting(kwargs, current_gadget_name)

            elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]:
                gadget.onInteracting(kwargs, current_gadget_name)

                if reason == hou.uiEventReason.Changed:
                    gadget.onEndInteracting(kwargs, current_gadget_name)

        return True


class LineGadget(object):
    """ ラインドラッグガジェットのサポートを実装します。
    """
    def __init__(self, handle, scene_viewer, parms):
        super(LineGadget,self).__init__()

        self.handle = handle
        self.scene_viewer = scene_viewer
        self.dragger = hou.ViewerHandleDragger("dragger")
        self.gadget_dir = parms["gadget_dir"]
        self.gadget_name = parms["gadget_name"]
        self.gadget_color = parms["gadget_color"]
        self.parm_name = parms["parm_name"]
        self.parm_index = parms["parm_index"]

        self.color_options = ru.ColorOptions(self.scene_viewer)

        # ガジェットが利用可能な場合にのみ、そのガジェットを初期化します。
        try:
            self.gadget = self.handle.handle_gadgets[self.gadget_name]
        except:
            self.gadget = None

        if self.gadget:
            sop_cat = hou.sopNodeTypeCategory()        
            verb = sop_cat.nodeVerb("line")
            verb.setParms(
                {   "origin" : (0,0,0),
                    "dir" : (self.gadget_dir[0],self.gadget_dir[1],self.gadget_dir[2]),
                    "dist": 1.0,
                    "points": 2
                })
            geo = hou.Geometry()
            verb.execute(geo, [])

            self.gadget.setParams({"draw_color": self.color_options.colorFromName(self.gadget_color)})
            self.gadget.setGeometry(geo)

    def _accept(self, gadget_name):
        return gadget_name == self.gadget_name

    def show(self, value):
        try:
            self.gadget.show(value)
        except:
            pass

    def onBeginInteracting(self, kwargs, gadget_name):
        if not self._accept(gadget_name):
            self.show(False)
            return False

        ui_event = kwargs["ui_event"]
        current_pos = self.handle.xform.extractTranslates()
        self.dragger.startDragAlongLine(ui_event, current_pos, self.gadget_dir)
        self.show(True)

        return True

    def onInteracting(self, kwargs, gadget_name):
        if not self._accept(gadget_name):
            return False

        ui_event = kwargs["ui_event"]
        drag_values = self.dragger.drag(ui_event)
        delta_pos = drag_values["delta_position"]

        self.handle.handle_parms[self.parm_name]["value"] += delta_pos[self.parm_index]

        # ガジェットのトランスフォームを更新します。
        xform = self.gadget.transform() * hou.hmath.buildTranslate(delta_pos)
        self.gadget.setTransform(xform)
        self.handle.xform = xform

        return True

    def onEndInteracting(self, kwargs, gadget_name):
        if not self._accept(gadget_name):
            if self.gadget:
                current_pos = self.handle.xform.extractTranslates()
                xform = hou.hmath.buildTranslate(current_pos)
                self.gadget.setTransform(xform)                    
                self.gadget.show(True)

            return False

        self.dragger.endDrag()

        return True

    def onDraw(self, draw_handle):
        try:
            self.gadget.draw(draw_handle)
        except:
            pass

Pythonスクリプト

はじめよう

次のステップ

リファレンス

  • hou

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

導師レベル

Python Viewerステート

Pythonビューアハンドル

プラグインタイプ