Houdini 19.0 Pythonスクリプト

Pythonハンドル ガジェット

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

On this page

Pythonビューアハンドル

概要

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

Gadget(ガジェット) とは、Pythonハンドルの視覚的なコンポーネントを表現したジオメトリのことです。 カスタムの移動ハンドルを例に挙げると、ガジェットを使用することで、オブジェクトを移動させるハンドルピボットを表現することができます。 スライダハンドルを例に挙げると、ガジェットはパラメータを変更するノブ(ツマミ)です。 Pythonハンドルは典型的には1個以上のガジェットを使用してそれらのガジェットの見た目をレンダリングします。

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を参照してください。

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

As for any builtin handles, node parameters can be bound to python handles either interactively with the Handle Bindings dialog or programmatically with a python state. The gadgets used in a python handle implementation to interact visually with the bound parameter(s) will then be enabled for drawing, interacting, etc.

But what if the user chooses to bound only some of the python handle parameters and leaves the rest unbound ? The gadgets that require these “unbound” parameters in a python handle implementation are of no use by the user since they can’t modify parameters, we certainly don’t want the user to believe these gadgets are still usable in the viewport.

What the python handle implementation should do in this case is to simply hide these "unbound" gadgets. The way for a python handle to know if a gadget is available or not at runtime is to provide a list of required parameters with hou.ViewerHandleTemplate.bindGadget(). With this information in hand, Houdini will be able to make a gadget available when all its required parameters are bound or make it unavailable otherwise.

Registering a gadget with its list of required handle parameters is optional but strongly recommended. It will make your handle more dynamic and friendly to users.

Here’s a more advanced example to demonstrate the implementation of a python handle with "binding aware" gadgets.

Note

The example seems a bit intimidating but the code used in onMouseEvent is very repetitive. This is done on purpose to better show the detail of a python handle implementation.

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

    # Register the handle parameters
    template.bindParameter( hou.parmTemplateType.Float, name="X", default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="Y", default_value=0.0 )

    # Register the gadgets with a list of required handle parameters
    template.bindGadget( hou.drawableGeometryType.Line, "x_gadget", parms=["X"] )
    template.bindGadget( hou.drawableGeometryType.Line, "y_gadget", parms=["Y"] )

    # Export the parameters to 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")

        # Set the gadgets with the Houdini standard colors
        self.color_options = ru.ColorOptions(self.scene_viewer)

        # Initialize the gadgets only if they are available
        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 should be set to None if the gadget is not available.
        current_gadget_name = self.handle_context.gadget()
        current_gadget = self._gadget(current_gadget_name)

        if current_gadget_name in ["x_gadget"]:

            # Handle the x gadget
            if reason == hou.uiEventReason.Start:

                # Move along x axis
                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"]

                # update the handle parms while dragging
                self.current_pos += delta_pos
                self.handle_parms["X"]["value"] += delta_pos[0]

                # update the gadget transform
                xform = current_gadget.transform() * hou.hmath.buildTranslate(delta_pos)
                current_gadget.setTransform(xform)

                if reason == hou.uiEventReason.Changed:
                    # ends dragging     
                    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"]:

            # Handle the y gadget
            if reason == hou.uiEventReason.Start:

                # Move along y axis
                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"]

                # update the handle parms while dragging
                self.current_pos += delta_pos
                self.handle_parms["Y"]["value"] += delta_pos[1]

                # update the gadget transform
                xform = current_gadget.transform() * hou.hmath.buildTranslate(delta_pos)
                current_gadget.setTransform(xform)

                if reason == hou.uiEventReason.Changed:
                    # ends dragging
                    self.xy_dragger
                    self.xy_dragger.endDrag()

                    # update the x gadget line
                    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 each gadget
        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)

The following example uses a modular approach to simplify the implementation of the previous example.

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

    # Register the handle parameters
    template.bindParameter( hou.parmTemplateType.Float, name="X", default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="Y", default_value=0.0 )

    # Register the gadgets with a list of required handle parameters
    template.bindGadget( hou.drawableGeometryType.Line, "x_gadget", parms=["X"] )
    template.bindGadget( hou.drawableGeometryType.Line, "y_gadget", parms=["Y"] )

    # Export the parameters to 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):
    """ Implement the support for a line dragging gadget.
    """
    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)

        # Initialize the gadget only if available        
        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]

        # update the gadget transform
        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ビューアハンドル

Pythonスクリプト

はじめよう

次のステップ

リファレンス

  • hou

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

導師レベル

Python Viewerステート

Pythonビューアハンドル

プラグインタイプ