| 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