Houdini 20.0 Pythonスクリプト

Pythonステート 選択

SOPノードステートの動作の一部としてユーザがジオメトリを選択できるようにする方法。

On this page

概要

SOPフィルターノードのワークフローでは、そのノードを動作させるコンポーネント(例えば、ポリゴンやポイント)をユーザに選択させることが多いです。 そのようなツールでは複数選択を許容/必須にする場合があり、例えば、“Copy to Points”ツールだと、コピーするジオメトリとコピー先のポイントを受け取ります。 選択したコンポーネントは、新しく作成されたノードの“Group”パラメータに入力します。

Pythonステートを使った独自ツールで選択に対応させる方法がいくつかあります:

Pythonステート用の高度な選択機能:

  • Volatile選択をハンドルする方法。Pythonステートがアクティブになった時にVolatile選択にアクセスすることができます。

  • 既存選択をハンドルする方法。Pythonステートがアクティブになった時に既存選択にアクセスすることができます。

  • Secure選択サポート。セレクターをビューアのSecure選択に準拠させるかどうか指定することができます。

手動型のスクリプトによる選択

これは、HOMスクリプトを使ってシェルフツールスクリプト(通常ではアセットに埋め込みます)でユーザに選択をお願いして、ユーティリティ関数を使ってノードを作成してから、そのノードのステートにする従来のHoudiniのワークフローです。

ツールスクリプトの書き方に関する詳細は、ツールスクリプトを参照してください。 アセットタイプのカスタムPythonステートを記述する方法の詳細は、Pythonのステートとノードを参照してください。

import stateutils
import soptoolutils

pane = stateutils.activePane()
# (ネットワークエディタ内でノードを配置するのではなく)ビューア内にいる場合は、セレクタースクリプトのみを実行します。
if isinstance(pane, hou.SceneViewer):
    # まず最初に、コピーするプリミティブ(s)を尋ねます。
    source = stateutils.Selector(
        name="select_polys",
        geometry_types=[hou.geometryType.Primitives],
        prompt="Select primitive(s) to copy, then press Enter",
        primitive_types=[hou.primType.Polygon],
        # プリミティブ番号を入力するパラメータ
        group_parm_name="sourcegroup",
        # この選択の接続先となる新しいノードの入力
        input_index=0,
        input_required=True,
    )
    # 次に、コピー先のポイントを尋ねます。
    target = stateutils.Selector(
        name="select_points",
        geometry_types=[hou.geometryType.Points],
        prompt="Select points to copy onto, then press Enter",
        group_parm_name="targetgroup",
        # 各選択を正しい入力に接続することを忘れないでください。 :)
        input_index=1,
        input_required=True,
    )

    # この関数は、Selectorオブジェクトのリストを受け取り、それぞれの選択をユーザに促します。
    container, selections = stateutils.runSelectors(
        pane, [source, target], allow_obj_selection=True
    )

    # この関数は、runSelectors()からのコンテナと選択を受け取り、
    # マージと作成先のコンテキストを考慮して新しいノードを作成します。
    newnode = stateutils.createFilterSop(
        kwargs, "$HDA_NAME", container, selections
    )
    # 最後に、このノードのステートにします。
    pane.enterCurrentNodeState()

else:
    # ビューア以外での操作に関しては、ローレベルの関数で代用します。
    soptoolutils.genericTool(kwargs, "$HDA_NAME")

ジオメトリセレクターのバインド

hou.ViewerStateTemplate.bindGeometrySelectorを使用することで、ビューアがカスタムSOPステートになった時に実行するSOPレベルの単一セレクターをバインドすることができます。 ユーザが選択を完了すると、Houdiniは、あなたのステートのonSelection()コールバックメソッドをコールします。

onSelection()メソッドでは、そのメソッドに渡される辞書からselectionアイテム(hou.GeometrySelectionオブジェクト)を取得することができます。 その辞書のnameから現行セレクターを取得することもできます。 その選択を受け取りたいのであれば、そのメソッドでTrueを返すことで、Houdiniはセレクターを終了して、あなたのステートの通常の処理に戻ります。 Falseを返すことで、その選択を拒否して、セレクターの実行を継続させることができます。

これは、ノードを使用しないステートで役に立ちます。例えば、表示ジオメトリからコンポーネントを選択し、それらのコンポーネントの情報を表示するステートがそうです。

Houdiniは、複数のジオメトリセレクターをステートにバインドすることができ、hou.SceneViewer.triggerStateSelectorを使って、個々のセレクターを開始/終了することができます。 バインド時に設定可能な名前によって、それらのセレクターがトリガーされます。

色々な選択ハンドラーの詳細は、選択イベントハンドラーを参照してください。

import hou

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onSelection(self, kwargs):
        sel = kwargs["selection"]
        sel_name = kwargs["name"]
        if sel_name == "selector1":
            # 3つのポリゴンを含んでいる選択のみを受け入れます。
            are_prims = sel.geometryType() == hou.geometryType.Primitive
            are_all_polys = all(pt == hou.primType.Polygon for pt in sel.primitiveTypes())
            selection = sel.selections()[0]
            count = selection.numSelected()

            return are_prims and are_all_polys and count == 3

        count = 0
        if sel_name == "selector2":
            # selector2 support
            selection = sel.selections()[0]
            count = selection.numSelected()
        return count > 0

template = hou.ViewerStateTemplate(
    "mystate", "My State",
    hou.sopNodeTypeCategory()
)
template.bindFactory(MyState)

# selector #1
template.bindGeometrySelector(
    name="selector1",
    prompt="Select three polygons",
    quick_select=False,
    use_existing_selection=True,
    geometry_types=hou.geometryType.Primitives,
    primitive_types=hou.primType.Polygon,
    allow_other_sops=False
)

# selector #2
template.bindGeometrySelector(
    name="selector2",
    prompt="Select a primitive",
    quick_select=True,
    geometry_types=hou.geometryType.Primitives
)

hou.ViewerStateTemplate.bindGeometrySelectorメソッドの引数に関する情報は、そのヘルプを参照してください。

オブジェクトセレクターのバインド

hou.ViewerStateTemplate.bindObjectSelectorを使用することで、ビューアがカスタムOBJステートになった時に実行するOBJレベルの単一セレクターをバインドすることができます。 ユーザが選択を完了すると、Houdiniは、あなたのステートのonSelection()コールバックメソッドをコールします。

onSelection()メソッドでは、そのメソッドに渡される辞書からselectionアイテム(hou.OpNodeオブジェクトのリスト)を取得することができます。 その選択を受け取りたいのであれば、そのメソッドでTrueを返すことで、Houdiniはセレクターを終了して、あなたのステートの通常の処理に戻ります。 Falseを返すことで、その選択を拒否して、セレクターの実行を継続させることができます。

Houdiniは、複数のオブジェクトセレクターをステートにバインドすることができ、hou.SceneViewer.triggerStateSelectorを使って、個々のセレクターを開始/終了することができます。 バインド時に設定可能な名前によって、それらのセレクターがトリガーされます。

class MyOBJState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onSelection(self, kwargs):
        cameras = kwargs["selection"]
        print "Number of cameras selected: ", len(cameras)

        return True


template = hou.ViewerStateTemplate("myOBJstate", "My OBJ State", hou.objNodeTypeCategory())
template.bindFactory(MyOBJState)

# カメラオブジェクトだけを選択するセレクターを追加します。
template.bindObjectSelector(
    prompt="Select camera object(s) and press Enter",
    quick_select=False,
    use_existing_selection=True,
    allowed_types=('*cam*',))

hou.ViewerStateTemplate.bindObjectSelectorメソッドの引数に関する情報は、そのヘルプを参照してください。

Drawableセレクターのバインド

Drawableセレクターは、Pythonステートで生成されたhou.GeometryDrawablehou.SimpleDrawableのエンティティを選択することができます。 このDrawableセレクターをPythonステートにバインドするには、hou.ViewerStateTemplate.bindDrawableSelectorを使用します。

Drawableセレクターは、たくさんの局面においてジオメトリセレクターやオブジェクトセレクターに似ています:

  • Drawableコンポーネントは、矩形選択や縄選択などのHoudini Selectツールオプションを使用して選択またはロケート(マウス下の検索)することができます。

  • Drawableコンポーネントの選択とロケートは、Houdiniで検知されます。

  • DrawableセレクターはPythonステート登録時にそのPythonステートにバインドすることができます。

  • Drawable選択が発生するとonSelectionハンドラーがコールされます。

しかし、通常のセレクターと異なる局面もあります:

  • Pythonステートは、ロケートや選択されたDrawableエレメントを描画する役割を担っています。

  • Drawableコンポーネントがロケートされると(マウスが上に乗ると)、onLocateSelectionハンドラーがコールされます。このハンドラーによって、Pythonステートがジオメトリコンポーネントをハイライトさせることができます。

  • マウスをジオメトリ上に乗せた時にDrawableジオメトリのみが選択でき、他のシーンジオメトリは単に無視されます。

  • Selectツールのジオメトリモードメニューに関係なくDrawableコンポーネントを選択することができます。

  • Secure選択設定はDrawableセレクターに適用されません。

ジオメトリセレクターやオブジェクトセレクターと同様に、Pythonステートプラグイン側で新しい選択を受信するonSelectionハンドラーを実装することができます。 onSelectionハンドラーは、選択されたコンポーネントインデックスが格納されたPython辞書形式で新しい選択を取得します。 Pythonステートプラグイン側でマウスがDrawable上に置かれた時に新しいDrawableロケート情報を受信するonLocateSelectionハンドラーを実装することができます。 それぞれのハンドラーのkwargs辞書のdrawable_selectionエントリーには、新しい選択とロケート情報が格納されます。

Drawable選択フォーマット

onSelectionハンドラーは、Drawableコンポーネントが選択される度にコールされます。 onLocateSelectionハンドラーは、Drawableコンポーネントがロケートされる度に(マウスが上に乗る度に)コールされます。 どちらのハンドラーもPython辞書のkwargsdrawable_selectionエントリーにDrawableコンポーネント情報が受信されます。 そのDrawableコンポーネント情報のフォーマットは以下のとおりです:

Drawable辞書

コンポーネント

データ

Drawable名

face

Drawableジオメトリ上のプリミティブインデックスのリスト。

line

Drawableジオメトリ上のポイントインデックスのリスト。1番目と2番目のインデックスでラインが表現され、3番目と4番目のインデックスで別のラインが表現されていきます。

point

Drawableジオメトリ上のポイントインデックスのリスト。

drawable_selectionエントリーの例:

{
    "box_lines" : {
          "line" : [5, 7, 6, 4, 7, 6, 7, 2, 3, 6],
    },
    "simple_tube" : {
          "face" : [3, 8, 7, 6, 5, 4],
          "line" : [4, 19, 3, 4, 5, 20, 4, 5, 6, 21, 5, 6, 7, 22, 6, 7],
          "point" : [8, 4, 5, 6, 7],
    },
    "tube_lines" : {
          "line" : [1, 16, 0, 1, 15, 0, 1, 2, 17, 16, 14, 29, 13, 14, 14, 0],
    },
    "tube_points" : {
          "point" : [17, 14, 2, 1, 0, 16],
    }
}

以下の例では、Drawableセレクターを使用して、ロケートされたラインのポイントをハイライトさせます。

import hou

def createViewerStateTemplate():
    state_typename = "highlight_line_points"
    state_label = "Highlight Line Points Demo"
    state_cat = hou.sopNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon("MISC_python")

    template.bindDrawableSelector("Select a drawable component", 
        name="my_drawable_selector",
        auto_start=True, 
        drawable_mask=["box_lines"])

    return template

class State(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

        # ロケートされたワイヤーフレームのDrawableボックス
        self.box_lines = hou.GeometryDrawable(self.scene_viewer, 
            hou.drawableGeometryType.Line, "box_lines")
        self.box_lines.setParams({"color1":hou.Color(1,1,0)})

        # ロケートされたラインのポイントをハイライトさせるDrawable。
        self.box_points_h = hou.GeometryDrawable(self.scene_viewer, 
            hou.drawableGeometryType.Point, "box_points_h")
        self.box_points_h.setParams({"color1":hou.Color(1,0,0), "radius":7, 
            "style":hou.drawableGeometryPointStyle.LinearSquare})

    def onEnter(self, kwargs):
        verb = hou.sopNodeTypeCategory().nodeVerb("box")
        verb.setParms({"type" : 1, "divrate":(5,5,5), "size":(3,3,3), "t": (0,1,0) })
        geo = hou.Geometry()
        verb.execute(geo, [])

        self.box_lines.setGeometry(geo)
        self.box_points_h.setGeometry(geo)

        self.box_lines.show(True)

    def onDraw( self, kwargs ):
        handle = kwargs["draw_handle"]
        self.box_lines.draw(handle)    
        self.box_points_h.draw(handle)    

    def onLocatedSelection(self, kwargs):
        self.box_points_h.show(False)

        # 選択をループさせて、ハイライトさせるポイントを設定します。
        for k,v in kwargs["drawable_selection"].items():

            if k == "box_lines":
                if "line" in v:
                    indices = v["line"]
                    self.box_points_h.setParams({"indices":indices})
                    self.box_points_h.show(True)

    def onStopSelection(self, kwargs):
        selector_name = kwargs["name"]

        if selector_name == "my_drawable_selector":
            # ロケートされたコンポーネントをクリアします。
            self.box_points_h.show(False)

Drawableセレクターハンドラーの詳細は、選択イベントハンドラーを参照してください。

Pythonステートは、Selectツールメニューで選択された選択修飾子(Add,Toggle,Removeなど)にも反応することができます。 以下のサンプルは、修飾子に対して標準のHoudiniカラーを用意するviewerstate.utils.DrawableSelectorColorsクラスを使用することで、ロケートされたジオメトリを選択された修飾子で色分けします。

import hou
import viewerstate.utils as su

class State(object):

    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer
        self.selection = None

        # セレクターのカラーを管理します。
        self.colors = su.DrawableSelectorColors(self.scene_viewer)            

        # ピック/選択用のフェースDrawable。
        self.faces = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Face, 
                                        "faces")
        self.faces.setParams({"color1":(0,0,0,0.5)})

        # ハイライト用のフェース。
        self.faces_h = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Face, 
                                        "faces_h")
        # 選択用のフェース。
        self.faces_s = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Face, 
                                        "faces_s")
        self.faces_s.setParams({"color1":self.colors["s"]})

        # 選択不可なラインDrawable。
        self.lines = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Line, 
                                        "lines")
        self.lines.setParams({"color1":(1,1,1,0.5)})

    def onEnter(self, kwargs):
        verb = hou.sopNodeTypeCategory().nodeVerb('sphere')
        verb.setParms({"type" : 2, "rad":(1,1,1), "rows": 50, "cols": 50})
        geo = hou.Geometry()
        verb.execute(geo, [])

        self.faces.setGeometry(geo)        
        self.faces_h.setGeometry(geo)
        self.faces_s.setGeometry(geo)
        self.faces.show(True)

        self.lines.setGeometry(geo)
        self.lines.show(True)

    def onDraw( self, kwargs ):
        handle = kwargs["draw_handle"]
        self.faces.draw(handle)    
        self.faces_h.draw(handle)    
        self.faces_s.draw(handle)    

        self.lines.draw(handle)

    def onLocatedSelection(self, kwargs):        
        self.faces_h.show(False)

        # ロケートされたフェースをループさせてハイライトさせます。
        for k,v in kwargs["drawable_selection"].items():
            if k == "faces" and "face" in v:
                indices = v["face"]

                # 選択された修飾子に合わせてDrawableにフェースカラーを設定します。
                self.faces_h.setParams({"indices":indices, "color1":self.colors["face"]})
                self.faces_h.show(True)

    def onSelection(self, kwargs):
        if "drawable_selection" in kwargs:
            self.selection = kwargs["drawable_selection"]

            self.faces_h.show(False)
            self.faces_s.show(False)

            if not self.selection:
                return

            # 選択されたDrawableをループさせて、選択されたフェースを描画します。
            for k,v in self.selection.items():

                if k == "faces" and "face" in v:
                    indices = v["face"]
                    if len(indices):
                        self.faces_s.setParams({"indices":indices})
                        self.faces_s.show(True)

        return False

def createViewerStateTemplate():
    state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
    state_label = "Drawable selector modifier demo"
    state_cat = hou.sopNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon(kwargs["type"].icon())

    hk1 = su.hotkey(state_typename, "drawable selector", "1")

    template.bindDrawableSelector("Select a drawable component", 
        name="my_drawable_selector",
        auto_start=True, 
        drawable_mask=["faces"],
        hotkey=hk1)    

    return template

エントリーセレクターのバインド

現在のところ、このメソッドは推奨されていません

理論的には、上記のスクリプトのstateutils.Selectorオブジェクトとstateutils.runSelectors()と同様に、hou.ViewerStateTemplate.bindSelectorを使って複数のコンポーネントセレクターをステートにバインドすることができます。 ステートは、これらのセレクターを介して実行され、そのステートのonGenerate()コールバックでノードを作成することができます。

しかし、私どもは、それらの選択を手動でスクリプト化し、上記で説明しているようにユーティリティ関数を使ってノードを作成することを推奨しています。

Volatile選択のハンドル

Volatile選択(つまり、selectステートを介した選択)は、Viewerステートによって単にonSelectionハンドラーを実装するだけで可能です。 これは、指定したカテゴリのViewerステートセレクターまたはDrawableセレクターをバインドする際に通常実装するものと同じハンドルです。 しかし、Volatile選択に対応させるために何もセレクターをバインドさせる必要はありません。 選択ハンドラーに関する詳細は、ここを参照してください。

Sキーを押してVolatile選択を終了させると、選択されているエレメントを入力としてonSelectionがコールされます。 このコールバックはPythonステートセレクター専用に設計されており、Volatileセレクターの終了前にコールされないので、 onStartSelectionハンドラーの実装はVolatile選択には何の役にも立ちません。 これはonStopSelectionでも同様です。

既存選択のハンドル

ステートになった時に現行選択にアクセスすることができます。 ステートになった時に既に選択が存在していた場合、Houdiniは、その選択されたエレメント(s)をonSelectionハンドラーに渡して、自動的にその選択を使用するようになります。

Volatile選択と同様に、既存選択をハンドルするためにセレクターをバインドする必要はなくて、単にonSelectionを実装するだけで十分です。 1つ以上のセレクターがバインドされている場合、Houdiniは、auto_startセレクターまたはuse_existing_selector=Trueで設定された最初のセレクターを使用するようになります。

Secure選択サポート

セレクターが開始した時に、セレクターを現行ビューアの Secure選択 に準拠させるのかしないのかを設定することができます。 Secure選択は、例えば、ユーザがハンドルをクリックしたつもりが不意に選択を変更してしまわないようにするのに役立ちます。

セレクターは、以下のSecure選択オプションのどれかを使って登録することができます:

  • Obey: セレクターが現行ビューアのSecure選択設定に準拠します。これがデフォルトのオプションです。

  • On: セレクターが開始すると、セレクターが現行ビューアのSecure選択をOnに設定します。このオプションを使用すると、ステートを出た時に以前のビューアのSecure選択設定が復元されます。

  • Off: セレクターが開始すると、セレクターが現行ビューアのSecure選択をOffに設定します。このオプションを使用すると、ステートを出た時に以前のビューアのSecure選択設定が復元されます。

  • Ignore: このオプションを使用すると、セレクターはビューアのSecure選択設定を無視し、常にエレメントを選択することができます。

Secure選択が有効な時、アクティブセレクターは受け身モードなので、ユーザは現行選択を変更することができません。 Secure選択が無効な時、ユーザは現行選択を変更することができます。

詳細は、以下を参照してください:

Pythonスクリプト

はじめよう

次のステップ

リファレンス

  • hou

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

導師レベル

Python Viewerステート

Pythonビューアハンドル

プラグインタイプ