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