On this page |
概要
(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