Houdini 19.5 Pythonスクリプト

Pythonステート ノードの作成と編集

ノードを制御するステートを実装する方法。

On this page

概要

(独自のViewer Stateを実装する基本的な方法は、Pythonステートを参照してください。)

現行ノードに関係なく同じ挙動をする“ノードを使用しない”カスタムステートを作成することができます。 しかし、ステートを特定のアセットに限定させて、そのアセットを編集できるようにしたいことが多いです。

例えば、シーン内に測定結果を表示するジオメトリ(2点間の距離を示した寸法線)を生成するアセットを作りたいとします。 そのアセットの場合、測定する開始点と終了点の位置を指定するためのパラメータが2つ必要です。 ユーザがビューア内でそれら2つのポイントをドラッグするとその測定結果が表示されるカスタムステートがあれば、そのアセットを使いやすくすることができます。

カスタムステートの実装の一部として、ノードを作成してステート状態にするツールスクリプトを記述し、そのステートの状態でユーザインターフェースに基づいてそのノードのパラメータを操作できるようにする必要があります。

一般的には、SOPアセット内部のネットワークがジオメトリの作成/制御の役割を担うようにしてください。 現在のところ、HOM APIにはジオメトリを扱うための込み入ったメソッドがあまりなく、反対に、SOPノードには非常に洗練された効率の良いジオメトリコードが膨大に用意されています。

Note

ステートを実装したインターフェースonGenerate()メソッドを使用することで、理論的にはそのステート内部からノードを作成することができます。 しかし、私達はツールスクリプトでノードを作成 してから 、そのステートの状態にすることを強く推奨します。

ツールスクリプト

アセットのType Propertiesウィンドウでは、 Tools タブのアセットと関連付けてシェルフツールを定義することができます。 ユーザがアセットをインストールすると、このツールがそのシェルフに追加できるようになります。

ツールスクリプトの基本的な書き方に関しては、ツールスクリプトの書き方を参照してください。

  • ユーザがそのシェルフ内でツールをクリックしたり、ビューア内で⇥ Tabメニューからツールを選択したり、ネットワークエディタ内で⇥ Tabメニューからアセットを選択すると、そのツールスクリプトが使用されます。

  • 現在のところ、カスタムPythonステートはSOPノードに対してのみ利用可能です 。シェルフツールスクリプトでは、ビューが既にSOPレベルにあるのかどうかをチェックし、SOPレベルでなかった場合は、アセットの作成先となるジオメトリオブジェクトを選択するようにユーザに促すか、または、アセットを格納する新しいジオメトリオブジェクトを作成するようにしてください。

  • 以下のサンプルでは、SOPノードの入力としてジオメトリを選択するように要求するコードを記述していません。選択に関しては、Pythonステートの選択を参照してください。

import stateutils
import soptoolutils


# アセットのイベントコールバックとは違い、このツールスクリプトは、["type"]からアセットタイプの参照を取得していません(その代わりに、kwargs["toolname"]からツール名を取得しています)。
# そのため、そのツール名からノード名を解釈する方法を用意するか、または、単に以下のように明示的にノード名を指定する必要があります。
nodetypename = "mynamespace::myasset::2.0"
# アセットの"ベース"名を使って、新しいオブジェクトの名前を付けます。
basename = "myasset"

# アクティブなペインの参照を取得します。このツールをネットワークエディタのTabメニューから選択した場合、そのペインはNetworkEditorになります。
pane = stateutils.activePane(kwargs)
if isinstance(pane, hou.SceneViewer):
    network = pane.pwd()
    if network.childTypeCategory() != hou.sopNodeTypeCategory():
        # まだオブジェクトの中にいないので、新しいSOPの作成先となる場所をユーザに尋ねます。
        objects = pane.selectObjects(
            prompt='Select objects',
            quick_select=False,
            use_existing_selection=True,
            allow_multisel=False,
            allowed_types=['geo']
        )
        if objects:
            # ユーザが2つ以上のオブジェクトを選択した場合は、1番目に選択したオブジェクトのみが取得されます。
            network = objects[0]
            # オブジェクトにジャンプします。
            pane.setPwd(network)
        else:
            # ユーザがオブジェクトを選択せずにEnterを押した場合は、
            # 新しいオブジェクトを作成します。
            cname = basename + "_object1"
            network = hou.node("/obj").createNode("geo", node_name=cname)

        # ネットワークの中に入ります。
        pane.setPwd(network)

    # 選択したネットワークの中に新しいノードを作成します(空っぽのリストは、コンポーネントが選択されていないことを意味します)。
    newnode = stateutils.createFilterSop(kwargs, nodetypename, network, [])
    # ノードタイプに関連付けられているデフォルトのステート(アセットが正しくセットアップされていれば、あなたのカスタムPythonステート)を起動します。
    pane.enterCurrentNodeState()
    # 別の方法として、以下のようにあなたのカスタムステートを明示的に設定することができます。
    # pane.setCurrentState("my_state_name")

else:
    # ビューア以外で操作した場合は、ローレベルの関数で代用します。
    soptoolutils.genericTool(kwargs, nodetypename)

コンテキストタブ

デフォルトでは、アセットに埋め込まれているツールは、そのノードで許可されているコンテキスト内でのみ利用可能です。 そのため、SOPアセットを配置したい時、デフォルトでは、そのアセットを作成するツールは、SOPネットワーク内でのみ利用可能です。

結局はSOPノードが作成されるとはいえ、上記のツールスクリプトは、オブジェクトレベルでもSOPレベルでも動作します (その理由は、オブジェクトレベルでツールスクリプトを実行すると、ユーザ入力を使って、既存のジオメトリノードまたは新しいジオメトリノードに入るからです)。

スクリプトがビューア内でのオブジェクトレベルのコールを制御できるようにした場合、オブジェクトコンテキストでもSOPコンテキストでもコールできるようにそのツールのデフォルトを変更してください:

  1. アセットのType Propertiesウィンドウで、 Tools タブをクリックします。

  2. その左側でデフォルトのツールを選択し、その右側で Context サブタブをクリックします。

  3. Contextタブの Viewer Pane サブタブをクリックします。

  4. OBJSOP を有効にします。

カスタムステートのパラメータを扱う方法

  • ステートの実装のほとんどのコールバックメソッドでは、そのコールバックメソッドに渡される辞書のnodeキーを使用することで(hou.Nodeオブジェクトとして)現行ノードを取得することができます。

  • ステートは、アセットに対してパラメータ値を読み込んだり設定することができます。これによって、ステートでユーザがビューア内でどのようにインタラクティブにアセットを編集できるのか決まります。

  • ステートでユーザがインタラクティブにパラメータを編集できるようにする標準的な方法は、アセットパラメータにステートのハンドルをバインドさせることです。しかし、必要であれば、ステートのローレベルのUIイベントインターフェースを使用することで、マウスの動き、マウスボタン、タブレットの圧力、シーン内のマウス下の内容などに応じて独自のインターフェースを作り上げることができます。

    単純なサンプルですが、以下のメソッドは、左マウスボタンが押されるとステート状態になり、そのノードの均一スケールパラメータを1.0に設定し、中マウスボタンを押すと、そのパラメータを2.0に設定します。

    def onMouseEvent(self, kwargs):
        device = kwargs["uv_event"].device()
        node = kwargs["node"]
    
        if device.isLeftButton():
            node.parm("scale").set(1.0)
        elif device.isMiddleButton():
            node.parm("scale").set(2.0)
    
  • パラメータインターフェースで非表示のパラメータをアセットに追加することができますが、ステートは、アセットネットワークでインタラクティブに操作してそのパラメータを変更することができます。

オペレーションツールバーをカスタマイズする方法

(ビューアの上部にある)オペレーションツールバーは、現行ステートに関連した設定を表示します。 アセットのステートがアクティブになった時にそのオペレーションツールバーに表示させる特定のパラメータをアセットから指定することができます。 これによって、重要/使用頻度の高いパラメータをインターフェース内のもっと目立つ所に格上げさせて、パラメータエディタを表示しなくてもそれらのパラメータをインタラクティブ操作で利用可能にすることができます。

To...Do this

パラメータをオペレーションツールバーに表示させる

  1. アセットのType Propertiesウィンドウを開きます。

  2. Parameters タブをクリックします。

  3. Existing Parameters からパラメータを選択します。

  4. Parameter Description から Show Parm In メニューを“Main & Tool Dialogs + Toolbox”に設定します。

Note

ネイティブのステートは、インタラクティブステート(インタラクティブ設定)にのみ適用されるコントロールをオペレーションツールバーに追加することができます。 このコントロールは、ノードのパラメータに呼応していません。現在のところ、これはカスタムPythonステートで利用不可ですが、将来のバージョンで追加したいと思っています。

サンプル: ポイントを描画する

ジオメトリまたはConstruction Planeをクリックすることで空間内に自由にポイントを作成することができる単純なSOPアセットを作成します。 このツールは、マウスクリックするとポイントが追加され、そのボタンを押している間はそのポイント位置をドラッグすることができます。

このアセットは、単に Points マルチパラメータをアセット上にプロモートさせたAdd SOPを含ませればよいだけです。 カスタムステートで、それらのポイントを作成するために、そのマルチパラメータを制御(インスタンスを追加してその位置を設定)します。

  • 興味ある人向けの練習問題: 単一ポイントを動かさずに、ユーザがドラッグした時にポイントの軌跡を描画させるには以下のコードをどのように変更すればよいでしょうか?条件として、“ストローク”で連続したポイントを生成するための近距離を指定するパラメータを用意するものとします。ヒント:hou.Vector3.distanceToを使用することで、2つの位置ベクトル間の距離を取得することができます。

  • beginStateUndo()endStateUndo()の使い方に注目してください。これは、マルチパラメータを加算し別々のUndoステップとしてポイント座標の(再)設定をするわけではなく、クリック/ドラッグの度にポイントを追加し、Undoが1回だけ可能です。詳細は、PythonステートのUndoを参照してください。

import stateutils

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

        self._node = None
        self._pressed = False
        self._index = 0    

    def _point_count(self):
        multiparm = self._node.parm("points")
        # これは、マルチパラメータのインスタンスの数を取得する方法です。
        return multiparm.evalAsInt()

    def _insert_point(self):
        index = self._point_count()
        multiparm = self._node.parm("points")
        multiparm.insertMultiParmInstance(index)
        return index

    def _set(self, index, position):
        self._node.parm("usept%d" % index).set(1)
        self._node.parmTuple("pt%d" % index).set(position)

    def _start(self):
        if not self._pressed:
            self.scene_viewer.beginStateUndo("Add point")
            self._index = self._insert_point()
        self._pressed = True

    def _finish(self):
        if self._pressed:
            self.scene_viewer.endStateUndo()
        self._pressed = False

    def onMouseEvent(self, kwargs):
        node = self._node = kwargs["node"]
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        origin, direction = ui_event.ray()

        # ジオメトリまたは地面との交差を求めます。
        intersected = -1
        inputs = node.inputs()
        # このノードに入力があれば、ジオメトリの交差のみを求めます。
        if inputs and inputs[0]:
            geometry = inputs[0].geometry()
            intersected, position, _, _ = stateutils.sopGeometryIntersection(geometry, origin, direction)
        if intersected < 0:
            position = stateutils.cplaneIntersection(self.scene_viewer, origin, direction)

        # LMBクリックされたら、ポイントを作成/移動させます。
        if device.isLeftButton():
            self._start()
            self._set(self._index, position)
        else:
            self._finish()

    def onInterrupt(self, kwargs):
        self._finish()

動作しているサンプルは、$HH/viewer_states/example/add_point_demo.hipをチェックしてください。 このデモは、HDA Embedded Pythonステートを使用しており、マウスでポイントを作成し、それらのポイントをSphere Drawableを使ってハイライトすることができます。

他にもViewer State Code GeneratorAdd Pointサンプルを使用することで、この軽量バージョンを作成することができます。

Pythonスクリプト

はじめよう

次のステップ

リファレンス

  • hou

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

導師レベル

Python Viewerステート

Pythonビューアハンドル

プラグインタイプ