Houdini 18.0 Pythonスクリプト

ツールスクリプト

シェルフツール用Pythonスクリプトを記述する方法。

On this page

概要

ツールスクリプトは、シェルフツールをクリックした時やビューア/ネットワークエディタ内のTabメニューからツールを選択した時に実行されます。 これらのツールは、設定の変更といった単純なものから、対話式にユーザが操作した結果からノードを生成するといった複雑なアクションまで対応しています。

Houdiniでツールスクリプトを記述できる場所が2つあります:

  • アセットでは、その内部にアセットの作成/編集を行なうツールを埋め込むことができます。ユーザはカスタムツールをシェルフタブに追加することができます。ツールスクリプトを使用することで、ビューア内またはネットワークエディタ内にアセットノードを作成することもできます。

  • カスタムツールを(アセットに埋め込まなくても)直接シェルフ上に作成することができます。このスクリプトは、ノードを作成したり、Pythonスクリプトを介して可能な他のアクションを実行することができます。

メモ

  • アセットを作成すると、Houdiniはデフォルトで基本的な操作を制御する汎用スクリプトで作られたツールを用意します。ユーザが対話的にツールを扱えるようにカスタマイズしない限りは、そのスクリプトを修正する必要はありません(Pythonステートも参照してください)。

  • ノードをシェルフ上にドラッグすると、Houdiniは自動的にそのタイプのノードを生成するシェルフアイテムを作成します。この新しいシェルフツールでは、デフォルトのツールスクリプトは 使用されません が、代わりに汎用スクリプトが使用されます。アセットに独自の操作性を加えたツールを追加した場合は、ノードをドラッグするのではなくて、シェルフにそのツールを追加してください。詳細は、シェルフをカスタマイズする方法を参照してください。

  • シェルフスクリプトを実行している間でもHoudiniは"活動中"になっています。これは、DOPオブジェクトを取得してからそのDOP Networkを変更する場合に非常に注意すべきことです。DOP Networkを変更すると、その現行フレームが再クックされるので、そのDOPオブジェクトが無効になってしまいます。

    現行フレームの再クックを回避するには、hou.setSimulationEnabled()をコールして、あなたが操作している間は、そのシミュレーションを無効にすることができます。それを無効にする前に必ず初期シミュレーション状態(hou.simulationEnabled())を記録してから、スクリプトの最後にそれを元に戻してください。

引数

Houdiniは、スクリプトをコールする時に、そのスクリプトのコンテキストにkwargsという名前の辞書変数を追加します。 この辞書には以下のキーが含まれています:

キー

タイプ

説明

pane

hou.PathBasedPaneTabサブクラス

ツールを呼び出した時のペイン。

  • Scene Viewer内でツールが呼び出された場合、これはhou.SceneViewerになります。

  • さらに、ユーザが Context Viewer(現行ネットワークに順応するビューアタイプ)からツールを呼び出した場合も想定しなければなりません。この場合だと

  • シェルフからツールが呼び出されると、これはNoneになります。この場合、ビューアが必要であれば、例えばスクリプトにhou.ui.paneTabOfType(hou.paneTabTypes.SceneViewer)を使ってそのビューアを検索しなければなりません。

paneの値が何であろうとも関数がシーンビューアを取得できるようにしたいのであれば、以下のscene_viewer()ユーティリティ関数を参照してください。

viewport

hou.GeometryViewport

ツールを呼び出した時のビューポート。 Scene Viewer(またはジオメトリを表示するContext Viewer)内でツールが呼び出されなかった場合は、これはNoneになります。

panename

str

ツールを呼び出した時のペインの名前(上記のpaneを参照)。

toolname

str

このツールの内部名。

shiftclick

bool

ユーザがツールをクリック(または⇥ Tabメニューから選択)した時に⇧ Shiftが押されているかどうか。

ctrlclick

bool

ユーザがツールをクリック(または⇥ Tabメニューから選択)した時に⌃ Ctrlが押されているかどうか。

altclick

bool

ユーザがツールをクリック(または⇥ Tabメニューから選択)した時にAlt(Macの場合は⌥ Option)が押されているかどうか。

cmdclick

bool

Macのみ 。ユーザがツールをクリック(または⇥ Tabメニューから選択)した時にが押されているかどうか。

requestnew

bool

ツールがノードから新しいインスタンスを作成するかどうか(通常)、または、既存ノードを再利用するかどうか(例えば、Edit SOPUV Edit SOPのノードはこれを利用可能)を指定します。

branch

bool

現在のディスプレイノードに追加するのではなくて、分岐させて新しいノードを作成するかどうか。

autoplace

bool

これをTrueに設定すると、ツールは、ユーザに配置位置を尋ねることなくネットワーク内にノードを配置します(これは、⌃ Ctrl/が押されたかどうかをチェックするのとは異なります)。

これは、配置とか修飾キーによるクリックとは関係のないツールを呼び出す時に設定します。 例えば、Tool Paletteからノードをネットワークエディタにドラッグする時です。

bbox

hou.BoundingBox

存在していないかもしれません 。このキーが辞書にあれば、その値は配置する際のオブジェクトの表示に使用する境界ボックスです。

この値はHoudiniから計算されるのではなく、.shelfファイルのbboxアトリビュートからツールによって設定されます(Houdiniは、シェイプツールが作成したジオメトリに基づいて内部的にこれを使ってbbox値を記録します)。

inputs

list of tuples of hou.Node and int

新しいノードの入力に接続したいノードと出力コネクタインデックスのリスト。ユーザがノード出力上でまたはを押した場合に、このリストに値が入り、ユーザ側でその出力と接続したいノードを選択することができます。

下位互換を持たせるために、kwargs辞書にはinputnodenameoutputindexもあります。これらのキーは、1個の入力ノードのみを指定する時に以前に使用されていました。

inputnodename

str

新しいノードの入力に接続するノードの名前または""(空っぽの文字列)。上記のinputsを参照してください。

outputindex

int

新しいノードの入力に接続するinputnodenameの出力インデックスまたは-1。上記のinputsを参照してください。

outputs

list of tuples of hou.Node and int

新しいノードの出力に接続したいノードと入力コネクタインデックスのリスト。ユーザがノード出力上でまたはを押した場合に、このリストに値が入り、ユーザ側でその入力と接続したいノードを選択することができます。

下位互換を持たせるために、kwargs辞書にはoutputnodenameinputindexもあります。これらのキーは、1個の出力ノードのみを指定する時に以前に使用されていました。

outputnodename

str

新しいノードの出力に接続するノードの名前または""(空っぽの文字列)。上記のoutputsを参照してください。

inputindex

int

新しいノードの入力に接続するoutputnodenameの入力インデックスまたは-1。上記のinputsを参照してください。

ユーティリティ関数

ツールスクリプトを構築する現在のAPIは非常にローレベルです(例えば、各スクリプトは、手動でワールド空間のUpベクトルをチェックしたり、Construction Planeの向きを変更したり、修飾キーをチェックしたり、ノードを正しい場所に接続したりといった役割をします)。 将来のバージョンのHoudiniでは、ハイレベルのHOM APIを使って、これをもっと簡単にできるようにしたいと思っています。

現在のところ、いくつかの詳細な処理を抽象化するためにstateutilsモジュールを用意しています。 これらの関数の使用方法は、How toセクションに載せています。stateutilsの関数は、SOPアセットのスクリプトに適しています。

"出荷時の"Houdiniシェルフツールは、色々な内部ライブラリ(toolutils, soputils, doputilsなど)を使用しています。 これらのライブラリのドキュメントはなくて、Pythonの使用方法に関する良いサンプルもなく、何の注意もなく変更/削除が行なわれています。 前述の通り、これらのライブラリをハイレベルのHOM APIに置換する計画があります。 しかし、現在のところ、場合によって、それらのライブラリから関数をコールする必要があります。 以下の"How to"セクションのコードスニペットは、ユーザがビューア内で操作しない時にこれらの関数を場合によってコールします。

How to

シーンビューを取得する

アクティブペインまたはhou.SceneViewerインスタンスの参照を取得するためのユーティリティ関数がいくつかあります。 これらの関数は、シーンをビューイングするコンテキストビューアなどの例外的なケースを考慮しています。

# ツールスクリプト内
import stateutils


# 現在アクティブなペインを取得します。kwargs["pane"]がNoneの場合(例えば、シェルフツールからスクリプトを実行した場合)、
# これはfindSceneViewer()をコールしてSceneViewerを検索します。
# それ以外の場合、これは他のペインタイプになります。例えば、ネットワークエディタ内でツールを選択した場合はhou.NetworkEditorになります。
# SceneViewerを想定する前に、そのタイプをチェックしてください。
pane = stateutils.activePane(kwargs)

# hou.SceneViewerを取得します。kwargs["pane"]がSceneViewerの場合、これはそのSceneViewerを返し、
# それ以外の場合、findSceneViewer()を使ってSceneViewerを検索します。
scene_viewer = stateutils.activeSceneViewer(kwargs['pane'])

# これは、まず最初に可視のシーンビューアを検索し、見つからなければ、可視でないシーンビューアを検索し、それを現行タブにします。
# デスクトップ内に何もSceneViewerがなければ、hou.NotAvailbleを引き起こします。
scene_viewer = stateutils.findSceneViewer()

ジェネレータSOPノードを配置する

ジェネレータ SOPとは、(入力ノードを修正するSOPとは対照的に)入力なしでデータを生成するSOPのことです。

# ツールスクリプト内
import soptoolutils
import stateutils


pane = stateutils.activePane(kwargs)
if isinstance(pane, hou.SceneViewer):
    # この関数は、配置位置を尋ね(ユーザがCtrl/Cmdをクリックした場合は自動配置になります)、Geometryオブジェクトを作成して、その中にあなたのSOPを配置します("Create in Context"設定も制御します)。
    stateutils.createGeneratorSop(
        kwargs, "$HDA_NAME", prompt="Select where to put the new thing"
    )
else:
    # ビューア以外での操作の場合、ローレベルの関数をコールします。
    soptoolutils.genericTool(kwargs, "$NODE_NAME")

フィルターノードを配置する

フィルター ノードとは、1本以上の入力を受け取り、それらを修正し、その結果を出力するノードのことです。

以下は、恒例のCopy to Points SOPに対してツールスクリプトを実装する方法を載せています。

# ツールスクリプト内
import soptoolutils
import stateutils


pane = stateutils.activePane(kwargs)
if isinstance(pane, hou.SceneViewer):
    # First we'll ask for the primitive(s) to copy
    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()からの選択を受け取り、
    # マージとCreate-in-Contextを考慮して新しいノードを作成します。
    newnode = stateutils.createFilterSop(
        kwargs, "$HDA_NAME", container, selections
    )
    # 最後に、このノードを現行状態にします。
    pane.enterCurrentNodeState()

else:
    # ビューア以外での操作の場合、ローレベルの関数をコールします。
    soptoolutils.genericTool(kwargs, "$HDA_NAME")

以下のスクリプトは、ユーザにオブジェクトの選択を促し(ビューアが既にオブジェクトの中に入っている場合は次に進みます)、どのコンポーネント選択を使わずにそのオブジェクトの中に新しいノードを作成する単純なサンプルです。 ユーザがジオメトリオブジェクトを選択せずにEnterを押すと、このツールは、勝手に新しいオブジェクトを作成します。 これは、入力にジオメトリを追加するものの選択を変更しないノードで役立ちます。

# ツールスクリプト内
import soptoolutils
import stateutils


_, _, basename, _ = hou.hda.componentsFromFullNodeTypeName("$HDA_NAME")


pane = stateutils.activePane(kwargs)
if isinstance(pane, hou.SceneViewer):
    # ユーザにコンポーネントを選択させるrunSelectors()を使用せずに、
    # 必要に応じてオブジェクトを選択させるようにしました。
    container = pane.pwd()
    if container.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個のオブジェクトのみを取得します。
            container = objects[0]
            # そのオブジェクトの中に入ります。
            pane.setPwd(container)
        else:
            # ユーザがオブジェクトを選択せずにEnterを押した場合は、新しいオブジェクトを作成します。
            cname = basename + "_object1"
            container = hou.node("/obj").createNode("geo", node_name=cname)

        # コンテナの中に入ります。
        pane.setPwd(container)

    # 選択したコンテナの中に新しいノードを作成します(空っぽのリストは、何もコンポーネントが選択されていないことを意味します)。
    newnode = stateutils.createFilterSop(kwargs, "$HDA_NAME", [])
    # 最後に、このノードを現行状態にします。
    pane.enterCurrentNodeState()

else:
    # ビューア以外での操作の場合、ローレベルの関数をコールします。
    soptoolutils.genericTool(kwargs, "$HDA_NAME")

メモ:

  • stateutils.runSelectors()allow_obj_selection引数がTrue(デフォルト)で、ユーザがオブジェクトレベルでツールを実行すると、そのスクリプトでは、ユーザは(コンポーネントではなくて)オブジェクトそのものを選択することができます。その引数がFalseの場合、ユーザがオブジェクトレベルでツールを実行すると、そのスクリプトはユーザにオブジェクトの選択を促し、そのオブジェクトの中に入って、コンポーネント選択を要求します。

  • 通常のHoudiniノード(例えば、アセット内のノード)と関連付けられたセレクターを実行したい場合、hou.NodeType.selectorsを使用することで、それらのセレクターのリストを取得することができます。

    selectors = nodetype.selectors()
    container, selections = runSelectors(scene_viewer, selectors)
    
  • 独自のプロンプトテキストを記述する時は、ユーザにEnterを押して選択を終了させることを伝えるのを忘れないようにしてください。

  • Pythonシェルウィンドウでscene_viewer.selectXXXメソッドをテストしてみると、フリーズまたは何も処理されないように見えます。これは、マウスカーソルがビューア上にある時にのみプロンプトが表示されるからです。

ユーザに位置、複数位置、経路を促す

Create シェルフタブのツールを使って新しいジオメトリを配置する時と同様に、ユーザにその位置を尋ねることができます。

"配置"の過程を持つツール(例えば、シーン内に新しい球を配置できるSphereツール)では、配置をスキップして"自然に"配置するかどうか設定することができます (例えばSphereツールの場合、これは原点に球を配置します。Cameraツールの場合、これは現行ビューに合うようにカメラを配置します)。 Linux/Windowsなら⌃ CtrlクリックまたはMacならクリックすることで、ユーザは"自動配置"を呼び出すことができます。両方をチェックしてください:

import stateutils


# hou.SceneViewerオブジェクトのselectPositions()メソッドは、(min_number_of_positionsとnumber_of_positionsのキーワードを使って)ユーザに特定の数の位置を促すことができ、
# オプションで複数位置を繋げたり(例えば、経路を促す時)、境界ボックスを表示(ジオメトリの配置を促す時)したりすることができます。

scene_viewer = stateutils.activeSceneViewer(kwargs['pane'])
if kwargs['ctrlclick'] or kwargs['cmdclick']:
    position, orientation = \
        stateutils.defaultPositionAndOrientation(scene_viewer)
else:
    # この結果は、Vector3オブジェクトのタプルです。
    positions = scene_viewer.selectPositions(
        prompt='Click to specify a position',
        number_of_positions=1,
        min_number_of_positions=-1,
        connect_positions=True,
        show_coordinates=True,
        bbox=kwargs.get("bbox", BoundingBox()),
        position_type=positionType.WorldSpace,
        icon=None,
        label=None
    )
    position = positions[0]

hou.SceneViewer.selectPositions(), hou.BoundingBox, hou.positionTypeのヘルプを参照してください。

ノードのパラメータを変更する

# ノードのパラメータすべてのリストを取得します
all_parms = my_cam.parms()

# パラメータの現行値を取得します
current_lookat = my_cam.parm("lookat").get()

# パラメータの値を設定します
my_cam.parm("lookat").set("/obj/torus1")

Tip

Node.parm()メソッドの引数には、パラメータの 内部名 を指定します。 パラメータの内部名を調べるには、パラメータエディタでそのラベル上にマウスカーソルを置くと表示されます。 この内部名は、ツールチップに表示されます。

ツールがコールされているネットワークコンテキストを検知する

2つ以上の異なるネットワークタイプ内で動作するツールを作成したいことがあります。 例えば、 Modify シェルフの Deleteアクションは、オブジェクトレベルでもジオメトリレベルでも動作し、オブジェクトレベルでは、選択したオブジェクトが削除され、ジオメトリレベルではBlast SOPが作成されます。

この部分のスクリプトを記述する方法がいくつかあります。 その1つは、Deleteアクションのスクリプトで使用されているコードに準拠することです:

# ツールスクリプト内
import stateutils


# 現在のコンテキストを調べます。ツール/アクションがコールされると、アクティブペインが返されます。
# その時にアクティブペインがなかった場合、これはNoneを返します。
scene_viewer = stateutils.active_scene_viewer(kwargs['pane'])

# アクティブペインがシーンビューアでなかった場合は、エラーを引き起こします。
if not scene_viewer
   raise hou.Error("The tool was not invoked in the scene viewer.")

# ビューアのネットワークコンテキストを取得します。
child_type = active_pane.pwd().childTypeCategory()

if child_type == hou.objNodeTypeCategory():
    ...
elif child_type == hou.sopNodeTypeCategory():
    ...
elif child_type == hou.dopNodeTypeCategory():
    ...

現行ビューポートを制御する

現行ビューポートを取得するには…

import stateutils

# シーンビューアを取得します。
scene_viewer = stateutils.find_scene_viewer()

# 現行ビューポートを取得します。
viewport = scene_viewer.curViewport()

GeometryViewportオブジェクトのsettings()メソッドをコールすると、GeometryViewportSettingsオブジェクトが返されます。 このオブジェクトには、ビューポートに関する情報を取得/設定するためのメソッドがたくさんあります。

# ビューポートの設定オブジェクトを取得する。
settings = viewport.settings()

この設定オブジェクトで最も役立つメソッドがviewTransform()setViewTransform()です。これらのメソッドは、ビューポートのトランスフォームマトリックスを取得/設定することができます。

ビューポートをカメラ視点に設定するには、以下のコードを使用します…

viewport.setCamera(camera_node)

現在のビューポートの視点になっているカメラノードを取得するには…

# カメラ視点でない場合はNoneを返します。
viewport.settings().camera()

ビューポートには、ビューをカメラまたはライトにコピーする特別なメソッドがあるので、 Lights and Cameras シェルフタブの標準ツールのCtrlクリックの挙動をコピーすることができます…

viewport.saveViewToCamera(cam_or_light_object)

Pythonスクリプト

はじめよう

次のステップ

Pythonビューアステート

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

導師レベル

リファレンス

  • hou

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

  • Alembic拡張関数

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