Houdini 18.0 Pythonスクリプト

Pythonステート コンテキストメニュー

独自ステートのコンテキストメニューをセットアップして、そのメニューを使ってユーザ操作に反応させる方法。

On this page

Pythonビューアステート

概要

hou.ViewerStateMenuオブジェクトを使用することで、ステートがアクティブ中にユーザがクリックした時に表示される独自のコンテキストメニューを構築することができます。

任意でonMenuPreOpenハンドラーを使って、カスタムコンテキストメニューのUIステートを管理することができます。

ステートのコンテキストメニューをデザインする方法に関しては、Viewer Stateのユーザインターフェースガイドラインを参照してください。

メニューを構築してバインドする方法

ステートテンプレートを生成するコードの部分で、hou.ViewerStateMenuオブジェクトを使ってコンテキストメニューを作成してから、 hou.ViewerStateTemplate.bindMenu()を使って、そのメニューをステートテンプレートにバインドさせます。

from __future__ import print_function


# ステートの挙動を実装したクラス
class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMenuAction(self, kwargs):
        print("You chose:", kwargs["menu_item"])


# ステートテンプレートを生成する関数。このステートテンプレートは、コンテキストメニューにバインドさせるステート、メニュー、ハンドル、セレクターなどを表現します。
def createViewerStateTemplate():
    # テンプレートオブジェクトを作成します。
    template = hou.ViewerStateTemplate(
        "mystate", "My State", hou.sopNodeTypeCategory()
    )
    # 実装したクラスをバインドします。
    template.bindFactory(MyState)

    # コンテキストメニューを作成して、バインドします。
    menu = hou.ViewerStateMenu('bend_menu', 'Bend')
    menu.addActionItem("first", "Action One")
    menu.addActionItem("second", "Action Two")
    menu.addActionItem("third", "Action Three")
    template.bindMenu(menu)

    return template

メニューを構築する方法は、hou.ViewerStateMenuオブジェクトを参照してください。

メニューのアクションに反応させる方法

ユーザがメニューを開いてアイテムを選択した時、Houdiniは、あなたのステートのonMenuActionメソッドをコールします。 このメソッドに渡される辞書には、選択したメニューアイテムのIDを含んだmenu_itemキーが含まれています。

Tip

これは、アクションメニューアイテムが選択されたタイミングのみを追跡し、ユーザの"設定"変更(チェックボックスのオン/オフ、ラジオ選択の変更)は無視します。 他のハンドラーメソッドでその設定項目の現行値を読み込んで、その設定を使用することで、そのステートの挙動に影響を与えることができます。

  • アクションメニューアイテムとトグルアイテムに関しては、その辞書のmenu_itemがアイテムに設定したIDと同じであれば、そのアイテムが選択されています。

    # メニューを作成する時
    menu = hou.ViewerStateMenu('bend_menu', 'Bend')
    menu.addActionItem("delete", "Delete Current")
    
    # ...
    
    # メニューハンドラーメソッド内
    def onMenuAction(self, kwargs):
        if kwargs["menu_item"] == "delete":
            # Code to implement the "delete" action
            # ...
    
  • 以下のようにメニュー内のすべてのアクションに対して面倒なif/elseツリーをコーディングするよりは:

    def onMenuAction(self, kwargs):
        menu_item = kwargs["menu_item"]
    
        # 以下のコードだと、メニューアイテムがたくさんあると読み書きが面倒です。
        if menu_item == "delete":
            # ...
        elif menu_item == "dup":
            # ...
        elif menu_item == "done":
            # ...
    

    何かしらのPythonテクニックを使うことで、メニューアイテムIDに基づいてアクションハンドルをメソッドに分けることができます:

    # メニューを作成する時。
    menu = hou.ViewerStateMenu('bend_menu', 'Bend')
    menu.addActionItem("delete", "Delete")
    menu.addActionItem("dup", "Duplicate")
    menu.addActionItem("done", "Finish")
    
    # ...
    
    # ステートクラス内
    class MyState(object):
        def __init__(self, state_name, scene_viewer):
            self.state_name = state_name
            self.scene_viewer = scene_viewer
    
        def onMenuAction(self, kwargs):
            # 選択したIDと同じ名前のメソッドをコールします
            # (ただし、メソッド名の頭にはアンダースコアを付けてください)
            menu_item = kwargs["menu_item"]
            method = getattr(self, "_" + menu_item)
            return method(kwargs)
    
        def _delete(self, kwargs):
            # ...
    
        def _dup(self, kwargs):
            # ...
    
        def _done(self, kwargs):
            # ...
    
  • トグルアイテムに関しては、メニューアイテムのIDをキーとして使用した辞書からトグルの新しいオン/オフの状態を取得することができます。

    # メニューを作成する時。
    menu = hou.ViewerStateMenu('bend_menu', 'Bend')
    menu.addToggleItem("showpoints", "Show Points")
    
    # ...
    
    # メニューハンドラーメソッド内
    def onMenuAction(self, kwargs):
        if kwargs["menu_item"] == "showpoints":
            # チェックボックスの現在の状態を読み込みます。
            showpoints = kwargs["showpoints"]
    
  • ラジオストリップアイテムに関しては、辞書内のmenu_itemラジオストリップ (個々のラジオアイテムではなく)に設定したIDと同じであれば、そのラジオストリップのアイテムが選択されています。

    ラジオストリップIDをキーとして使用した辞書から新しい現行アイテムインデックスを取得することができます。

    # メニューを作成する時。
    menu = hou.ViewerStateMenu('bend_menu', 'Bend')
    menu.addRadioStrip("deform_type", "Deformation Type", "bend")
    menu.addRadioStripItem("deform_type", "bend", "Bend")
    menu.addRadioStripItem("deform_type", "squash", "Squash")
    
    # ...
    
    # メニューハンドラーメソッド内
    def onMenuAction(self, kwargs):
        if kwargs["menu_item"] == "deform_type":
            # 現在選択されているアイテムのインデックスを取得します。
            current = kwargs["deform_type"]
    

メニュー設定を読み込む方法

コンテキストメニューにチェックボックスのメニューアイテムを追加(hou.ViewerStateMenu.addToggleItem()を使用)またはラジオボタンアイテムを追加(hou.ViewerStateMenu.addRadioStrip()を使用)すると、 Houdiniは、渡されたすべてのkwargs辞書内のそれらのアイテムの現在の状態をすべてのイベントハンドラーメソッドに渡します。

これによって、メニュー設定をチェックしてから特定のイベントの挙動を変更するのが簡単になります。 以下のサンプルのonMouseEventコードは、メニュー設定をチェックしてから、マウスクリックの挙動を変更しています。

from __future__ import print_function


# ステートの挙動を実装したクラス
class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        ui_event = kwargs["ui_event"]
        if ui_event.device().isLeftButton():
            # メニュー内の"Bend"チェックボックスアイテムが有効になっているかどうかチェックします。
            if kwargs["bend"]:
                option = kwargs["bend_parm_option"]
                # ...
            else:
                # ...


# ステートのコンテキストメニューを生成する関数。
def create_menu():
    menu = hou.ViewerStateMenu('bend_menu', 'Bend')

    menu.addToggleItem( 'bend', 'Bend', True )

    menu.addSeparator()

    select_parm_menu = hou.ViewerStateMenu( 'select_parm_menu', 'Select Parm...' )
    select_parm_menu.addRadioStrip( 'bend_parm_option', 'Bend Parm', 0 )  
    select_parm_menu.addRadioStripItem( 'bend_parm_option', 'bend', 'Bend' )
    select_parm_menu.addRadioStripItem( 'bend_parm_option', 'twist', 'Twist' )
    select_parm_menu.addRadioStripItem( 'bend_parm_option', 'lengthscale', 'Length Scale' )
    select_parm_menu.addRadioStripItem( 'bend_parm_option', 'taper', 'Taper' )
    select_parm_menu.addRadioStripItem( 'bend_parm_option', 'squish', 'Squish' )

    menu.addMenu(select_parm_menu)

    return menu


# ステートテンプレートを生成する関数。このステートテンプレートは、コンテキストメニューにバインドさせるステート、メニュー、ハンドル、セレクターなどを表現します。
def createViewerStateTemplate():
    # テンプレートオブジェクトを作成します。
    template = hou.ViewerStateTemplate(
        "mystate", "My State", hou.sopNodeTypeCategory()
    )
    # 実装したクラスをバインドします。
    template.bindFactory(MyState)

    # ハンドル、セレクター、メニューなどをバインドします。
    template.bindMenu(create_menu())

    return template

メニューホットキー

Note

Houdiniのホットキーシステムは、新しいシステムに移行中なので、将来のバージョンで置換される可能性があります。

  • Houdiniは、キー自体(例えば、shift+p)の代わりに記号(例えば、h.pane.gview.world.selectall)を使用してホットキー可能なアクションを表現していて、ユーザはホットキーエディタを使ってそれらのアクションを編集することができます。

    ステートメニューを構築する時は、独自のホットキー記号を作成し、それをステートコンテキストメニューのメニューアイテムに割り当てることができます。 これによって、ユーザがホットキーをメニューアイテムに割り当てることができます。

  • hou.ViewerStateMenu.addActionItem(), hou.ViewerStateMenu.addToggleItem(), hou.ViewerStateMenu.addRadioStripItem()のメソッドは、オプションの引数としてHoudiniの ホットキー記号 文字列を受け取ることができます。

  • ホットキー記号は、ドットの接頭辞を付けることで、そのホットキーが利用可能な コンテキスト を指定します(これによって、ユーザは同じホットキーにコンテキストの異なる別のアクションを割り当てることができます)。

    SOP Viewer Stateに関しては、そのホットキーの接頭辞は以下のようになります:

    h.pane.gview.state.sop.state_name.

    state_nameは、ステートテンプレートを作成する時に指定するステートの内部名です。

  • ホットキー記号を作成するには、コンテキストの最後に特定のアクションの名前を追加します。例えば、Deleteホットキーを指定したいのであれば、以下の記号を使用することができます:

    h.pane.gview.state.sop.state_name.delete

import hou

# ホットキーシンボルを作成します。

# まず最初にaddContextを使ってホットキーコンテキストを作成する必要があります。
key_context = "h.pane.gview.state.sop.mystate"
hou.hotkeys.addContext(
    key_context, "mystate Operation",
    "These keys apply to the mystate operation."
)

# その記号、名前、説明を使ってaddCommandをコールします。
del_key = key_context + ".delete"
hou.hotkeys.addCommand(del_key, "Delete", "Delete the current change")
dup_key = key_context + ".duplicate"
hou.hotkeys.addCommand(
    dup_key, "Duplicate", "Copies the current change into a new change"
)
end_key = key_context + ".finish"
hou.hotkeys.addCommand(end_key, "Finish", "Saves the current change")

# メニューを構築します。
menu = hou.ViewerStateMenu('bend_menu', 'Bend')
# 追加引数としてホットキー記号を使って、addなんちゃらItemメソッドをコールします。
menu.addActionItem("delete", "Delete", del_key)
menu.addActionItem("dup", "Duplicate", dup_key)
menu.addActionItem("done", "Finish", end_key)

メニューを更新する方法

Pythonステートのワークフローを改良するために、コンテキストメニューステートを動的に更新したいことがあります。 例えば、Houdiniの他のステートに基づいて、特定のメニューまたはメニュー項目を有効/無効にしたり、トグル項目のチェックマークを設定することができます。

onMenuPreOpenハンドラーは、まさにコンテキストメニューのUIステートを管理するためのツールです。 onMenuPreOpenを実装すると、Houdiniはメニューを表示する直前にそのハンドラーを呼び出して更新を実行することができます。

import hou

# ステートクラスの実装
class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

        # ハンドルアクセス
        self.hvector = hou.Handle(self.scene_viewer, 'Vector')
        self.hdist = hou.Handle(self.scene_viewer, 'Distance')
        self.hmodif = hou.Handle(self.scene_viewer, 'Modifier')
        self.hupdir = hou.Handle(self.scene_viewer, 'Up Direction')

    # メニューステートとメニュー項目ステートを更新します。
    def onMenuPreOpen( self, kwargs ):
        menu_id = kwargs['menu']
        node = kwargs['node']
        menu_states = kwargs['menu_states']
        menu_item_states = kwargs['menu_item_states']

        if menu_id == 'handle_show_menu':
            # ハンドルの可視プロパティを使って'handle_show_menu'項目を更新します。

            menu_item_states['show_distance_handle']['value'] = self.hdist.visible()
            menu_item_states['show_modifier_handle']['value'] = self.hmodif.visible()
            menu_item_states['show_up_direction_handle']['value'] = self.hupdir.visible()
            menu_item_states['show_vector_handle']['value'] = self.hvector.visible()

        elif menu_id == 'select_parm_menu':
            # 'select_parm_menu'メニューを有効にします。

            menu_states['enable'] = True

        elif menu_id == 'bend_menu':
            # 該当するノードパラメータのロックが解除されている場合、'bend_menu'トグル項目を有効にします。
            # そうでない場合、無効にします。

            bend = node.parm('bend')
            squish = node.parm('squish')
            twist = node.parm('twist')
            lengthscale = node.parm('lengthscale')
            taper = node.parm('taper')

            menu_item_states['bend_toggle_item']['enable'] = not bend.isLocked()
            menu_item_states['squish_toggle_item']['enable'] = not squish.isLocked()
            menu_item_states['twist_toggle_item']['enable'] = not twist.isLocked()
            menu_item_states['lengthscale_toggle_item']['enable'] = not lengthscale.isLocked()
            menu_item_states['taper_toggle_item']['enable'] = not taper.isLocked()

        elif menu_id == 'bend_parm_radio':
            # すべての項目を無効にすることで、'bend_parm_radio'メニューを読み込み専用にします...
            menu_states['enable'] = False

            # ...そして、最初に見つかったロック解除されているパラメータを使ってラジオ項目を選択します
            param = 'bend'
            if not node.parm('bend').isLocked():
                param = 'bend'
            elif not node.parm('squish').isLocked():
                param = 'squish'
            elif not node.parm('twist').isLocked():
                param = 'twist'
            elif not node.parm('lengthscale').isLocked():
                param = 'lengthscale'
            elif not node.parm('taper').isLocked():
                param = 'taper'

            # ロック解除されているパラメータ名を使ってラジオ項目を選択します
            menu_states['value'] = param

    # メニューアクションコールバック
    def onMenuAction( self, kwargs ):
        menu_item = kwargs['menu_item']

        if menu_item == 'show_vector_handle':
            self.hvector.show(kwargs['show_vector_handle'])
        elif menu_item == 'show_distance_handle':
            self.hdist.show(kwargs['show_distance_handle'])
        elif menu_item == 'show_up_direction_handle':
            self.hupdir.show(kwargs['show_up_direction_handle'])
        elif menu_item == 'show_modifier_handle':
            self.hmodif.show(kwargs['show_modifier_handle'])

    def onEnter( self, kwargs ):
        # このステートになった時にモディファイアHUDスライダハンドルを隠したい。
        self.hmodif.show( False );

# カスタムコンテキストメニューを作成します。
def createContextMenu():
    menu = hou.ViewerStateMenu('bend_menu', 'Bend')

    menu.addToggleItem( 'bend_toggle_item', 'Bend', True )
    menu.addToggleItem( 'twist_toggle_item', 'Twist', False )
    menu.addToggleItem( 'lengthscale_toggle_item', 'Length Scale', False )
    menu.addToggleItem( 'taper_toggle_item', 'Taper', False )
    menu.addToggleItem( 'squish_toggle_item', 'Squish', False )

    menu.addSeparator()

    select_parm_menu = hou.ViewerStateMenu( 'select_parm_menu', 'Select Parm to Modulate...' )
    select_parm_menu.addRadioStrip( 'bend_parm_radio', 'Bend Parm', 'bend' )  
    select_parm_menu.addRadioStripItem( 'bend_parm_radio', 'bend', 'Bend' )
    select_parm_menu.addRadioStripItem( 'bend_parm_radio', 'lengthscale', 'Length Scale' )
    select_parm_menu.addRadioStripItem( 'bend_parm_radio', 'squish', 'Squish' )
    select_parm_menu.addRadioStripItem( 'bend_parm_radio', 'taper', 'Taper' )
    select_parm_menu.addRadioStripItem( 'bend_parm_radio', 'twist', 'Twist' )
    menu.addMenu(select_parm_menu)

    menu.addSeparator()

    # ハンドル用ハンドルメニュー
    handle_show_menu = hou.ViewerStateMenu( 'handle_show_menu', 'Show Handles...' )
    handle_show_menu.addToggleItem( 'show_distance_handle', 'Show Distance', True )
    handle_show_menu.addToggleItem( 'show_modifier_handle', 'Show Modifier', False )
    handle_show_menu.addToggleItem( 'show_up_direction_handle', 'Show Up Direction', True )
    handle_show_menu.addToggleItem( 'show_vector_handle', 'Show Vector', True )
    menu.addMenu(handle_show_menu)    

    menu.addSeparator()

    return menu

# Pythonステートを登録します。
def createViewerStateTemplate():
    # テンプレートオブジェクトを作成します。
    template = hou.ViewerStateTemplate("mystate", "My State", hou.sopNodeTypeCategory())

    # 実装クラスをバインドします。
    template.bindFactory(MyState)

    # ステートにポップアップメニューを取り付けます。
    template.bindMenu(createContextMenu())

    # ハンドルをバインドします。
    template.bindHandle( 'vector', 'Vector', cache_previous_parms=True )
    template.bindHandle( 'distance', 'Distance' )
    template.bindHandle( 'hudslider', 'Modifier', cache_previous_parms=True, 
        settings="hudrangelow(0) hudrangehigh(5) hudlocklow(0) hudlockhigh(5)" )
    template.bindHandleStatic( 'vector', 'Up Direction', 
        [
                        ('group', 'input'),
                        ('originx', 'tx'),
                        ('originy', 'ty'),
                        ('originz', 'tz'),
                        ('upx', 'vx'),
                        ('upy', 'vy'),
                        ('upz', 'vz')
                ]
    )

    return template

Pythonビューアステート

Pythonスクリプト

はじめよう

次のステップ

Pythonビューアステート

Pythonでビューアステートを記述することで、ビューポート内でノードのユーザ操作をカスタマイズすることができます。

導師レベル

リファレンス

  • hou

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

  • Alembic拡張関数

    Alembicファイルから情報を抽出するための便利な関数です。