On this page |
概要 ¶
ツールスクリプトは、シェルフツールをクリックした時やビューア/ネットワークエディタ内のTabメニューからツールを選択した時に実行されます。 これらのツールは、設定の変更といった単純なものから、対話式にユーザが操作した結果からノードを生成するといった複雑なアクションまで対応しています。
Houdiniでツールスクリプトを記述できる場所が2つあります:
-
アセットでは、その内部にアセットの作成/編集を行なうツールを埋め込むことができます。ユーザはカスタムツールをシェルフタブに追加することができます。ツールスクリプトを使用することで、ビューア内またはネットワークエディタ内にアセットノードを作成することもできます。
-
カスタムツールを(アセットに埋め込まなくても)直接シェルフ上に作成することができます。このスクリプトは、ノードを作成したり、Pythonスクリプトを介して可能な他のアクションを実行することができます。
メモ ¶
-
アセットを作成すると、Houdiniはデフォルトで基本的な操作を制御する汎用スクリプトで作られたツールを用意します。ユーザが対話的にツールを扱えるようにカスタマイズしない限りは、そのスクリプトを修正する必要はありません(Pythonステートも参照してください)。
-
ノードをシェルフ上にドラッグすると、Houdiniは自動的にそのタイプのノードを生成するシェルフアイテムを作成します。この新しいシェルフツールでは、デフォルトのツールスクリプトは 使用されません が、代わりに汎用スクリプトが使用されます。アセットに独自の操作性を加えたツールを追加した場合は、ノードをドラッグするのではなくて、シェルフにそのツールを追加してください。詳細は、シェルフをカスタマイズする方法を参照してください。
-
シェルフスクリプトを実行している間でもHoudiniは“活動中”になっています。これは、DOPオブジェクトを取得してからそのDOP Networkを変更する場合に非常に注意すべきことです。DOP Networkを変更すると、その現行フレームが再クックされるので、そのDOPオブジェクトが無効になってしまいます。
現行フレームの再クックを回避するには、hou.setSimulationEnabled()をコールして、あなたが操作している間は、そのシミュレーションを無効にすることができます。それを無効にする前に必ず初期シミュレーション状態(hou.simulationEnabled())を記録してから、スクリプトの最後にそれを元に戻してください。
引数 ¶
Houdiniは、スクリプトをコールする時に、そのスクリプトのコンテキストにkwargs
という名前の辞書変数を追加します。
この辞書には以下のキーが含まれています:
キー |
タイプ |
説明 |
---|---|---|
|
hou.PathBasedPaneTabサブクラス |
ツールを呼び出した時のペイン。
|
|
ツールを呼び出した時のビューポート。
Scene Viewer(またはジオメトリを表示するContext Viewer)内でツールが呼び出されなかった場合は、これは |
|
|
|
ツールを呼び出した時のペインの名前(上記の |
|
|
このツールの内部名。 |
|
|
ユーザがツールをクリック(または⇥ Tabメニューから選択)した時に⇧ Shiftが押されているかどうか。 |
|
|
ユーザがツールをクリック(または⇥ Tabメニューから選択)した時に⌃ Ctrlが押されているかどうか。 |
|
|
ユーザがツールをクリック(または⇥ Tabメニューから選択)した時にAlt(Macの場合は⌥ Option)が押されているかどうか。 |
|
|
Macのみ 。ユーザがツールをクリック(または⇥ Tabメニューから選択)した時に⌘が押されているかどうか。 |
|
|
ツールがノードから新しいインスタンスを作成するかどうか(通常)、または、既存ノードを再利用するかどうか(例えば、Edit SOPやUV Edit SOPのノードはこれを利用可能)を指定します。 |
|
|
現在のディスプレイノードに追加するのではなくて、分岐させて新しいノードを作成するかどうか。 |
|
|
これを これは、配置とか修飾キーによるクリックとは関係のないツールを呼び出す時に設定します。 例えば、Tool Paletteからノードをネットワークエディタにドラッグする時です。 |
|
存在していないかもしれません 。このキーが辞書にあれば、その値は配置する際のオブジェクトの表示に使用する境界ボックスです。 この値はHoudiniから計算されるのではなく、 |
|
|
|
新しいノードの入力に接続したいノードと出力コネクタインデックスのリスト。ユーザがノード出力上でまたはを押した場合に、このリストに値が入り、ユーザ側でその出力と接続したいノードを選択することができます。 下位互換を持たせるために、 |
|
|
新しいノードの入力に接続するノードの名前または |
|
|
新しいノードの入力に接続する |
|
|
新しいノードの出力に接続したいノードと入力コネクタインデックスのリスト。ユーザがノード出力上でまたはを押した場合に、このリストに値が入り、ユーザ側でその入力と接続したいノードを選択することができます。 下位互換を持たせるために、 |
|
|
新しいノードの出力に接続するノードの名前または |
|
|
新しいノードの入力に接続する |
ユーティリティ関数 ¶
ツールスクリプトを構築する現在の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): # まず最初に、コピーするプリミティブ(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()からの選択を受け取り、 # マージと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)