必要になった経緯
他人からhipファイルを渡された時に複雑なノードネットワークの中でアトリビュートの起点や変更を知りたい時に手動だと大変な場合があります。ノードの右下に付与されたアトリビュート名が表示されるのでそれを目印に探すことは可能ですが、大規模ネットワークだと少々手間です。
また、デバッグや最適化をする際にもすぐに起点、変更ポイントに行きたくても時間がかかります。
このような課題を解決するため、 「アトリビュートが付与されたノードを一撃で特定するツール」 を開発しました。
アトリビュートがどこで付与されたかを探る手段
Houdiniの標準機能では、以下の方法で確認することができます:
-
手動で上流ノードを一つずつ確認
- ノードのパラメータやGeometry情報をチェックします。
- ネットワークが大きい場合、非常に手間がかかります。
-
Geometry Spreadsheetを利用
- アトリビュートの存在や値を直接確認できますが、どのノードで付与されたかを知るには上流ノードを順番に選択し、スプレッドシートでアトリビュートの有無や値の変化を確認する必要があります。
-
Attribute CreateやWrangleノードを探す
- アトリビュートは通常、以下のようなノードで作成されます
- Attribute Create
- Attribute Wrangle
- Attribute Expression
- VOPノード(Attribute VOP)
- ネットワーク内でこれらのノードを探し、特定のアトリビュートが操作されている箇所を確認しますが、手動では時間がかかります。
- アトリビュートは通常、以下のようなノードで作成されます
-
Node Infoを利用する
- 現在のノードを選択し、右クリックしてNode Infoを開きます。
- 情報に表示されるアトリビュート名や依存関係をチェックします。
- こちらも手動では時間がかかります。
これらの手段には限界があるため、効率的にアトリビュート付与元を探索するツールが必要でした。
コードの特徴と仕組み
開発したツールは、選択したノードを起点に上流のノードネットワークを再帰的に探索し、指定したアトリビュートが初めて生成されたノードを特定するものです。
さらに、ユーザーが入力しやすいようポップアップUIを実装し、選択状態やフォーカスの視覚的フィードバックも提供します。
コードの仕組み
1. 上流をたどってアトリビュートを探すロジック
def find_attribute_origin(node, attrib_name, attrib_type):
visited = set() # 既にチェックしたノードを記録
def traverse_upstream(current_node):
if current_node in visited: # ループ回避
return None
visited.add(current_node)
try:
geo = current_node.geometry()
except hou.GeometryPermissionError:
return None
attrib_list = {
"Point": geo.pointAttribs,
"Primitive": geo.primAttribs,
"Vertex": geo.vertexAttribs,
"Detail": geo.globalAttribs
}.get(attrib_type, lambda: [])()
if any(attrib.name() == attrib_name for attrib in attrib_list):
input_nodes = current_node.inputs()
if not input_nodes:
return current_node
for input_node in input_nodes:
origin = traverse_upstream(input_node)
if origin:
return origin
return current_node
return None
return traverse_upstream(node)
-
再帰的な探索:
inputs()
で上流ノードを辿り、アトリビュートが存在するかチェックします。 -
ループ回避:
visited
セットを使用して、循環するネットワークでも無限ループにならないようにします。 - アトリビュートの存在チェック:指定されたタイプ(Point, Primitive, Vertex, Detail)でアトリビュートを検索します。
2. ユーザー入力ポップアップUI
result = hou.ui.readMultiInput(
"Search Attribute Origin",
input_labels=["Attribute Name", "Attribute Type (Point/Primitive/Vertex/Detail)"],
buttons=("OK", "Cancel"),
close_choice=1,
initial_contents=["my_attribute", "Point"]
)
-
readMultiInput
の使用:ポップアップUIでアトリビュート名とタイプをユーザーが入力できます。 -
初期値設定:
initial_contents
でデフォルト値を提供し、入力の手間を減らします。
3. 結果のノード選択とフォーカス
if origin_node:
origin_node.setSelected(True, clear_all_selected=True)
network_editor = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
if network_editor:
network_editor.setCurrentNode(origin_node)
-
選択状態の設定:
setSelected(True)
で該当ノードを選択状態にします。 -
ネットワークビューのフォーカス:
setCurrentNode()
でネットワークエディタを対象ノードにフォーカスします。
今回作成したツール Attribute Tracking
def find_attribute_origin(node, attrib_name, attrib_type):
"""
現在のノードから上流をたどり、指定されたアトリビュートを初めて生成したノードを探す。
"""
visited = set() # 既にチェックしたノードを記録
def traverse_upstream(current_node):
# ノードがすでにチェック済みの場合はスキップ
if current_node in visited:
return None
visited.add(current_node)
# 現在のノードのGeometryを取得
try:
geo = current_node.geometry()
except hou.GeometryPermissionError:
return None # Geometryが存在しない場合はスキップ
# アトリビュートが存在するかチェック
attrib_list = {
"Point": geo.pointAttribs,
"Primitive": geo.primAttribs,
"Vertex": geo.vertexAttribs,
"Detail": geo.globalAttribs
}.get(attrib_type, lambda: [])()
if any(attrib.name() == attrib_name for attrib in attrib_list): # 指定タイプでアトリビュートが存在するか
# このノードに到達する前に上流をさらにチェック
input_nodes = current_node.inputs()
if not input_nodes: # 入力ノードが無い場合、このノードが起点
return current_node
# 上流ノードを再帰的にチェック
for input_node in input_nodes:
origin = traverse_upstream(input_node)
if origin:
return origin
return current_node # 上流に該当ノードが無い場合、現在のノードが起点
return None # アトリビュートが無い場合はスキップ
return traverse_upstream(node)
# ポップアップUIを再構築
result = hou.ui.readMultiInput(
"Search Attribute Origin",
input_labels=["Attribute Name", "Attribute Type (Point/Primitive/Vertex/Detail)"],
buttons=("OK", "Cancel"),
close_choice=1,
initial_contents=["my_attribute", "Point"] # アトリビュート名とタイプの既定値
)
# 入力内容を取得
if result[0] == 1: # キャンセルが押された場合
hou.ui.displayMessage("Operation cancelled.")
else:
attribute_name = result[1][0]
attribute_type = result[1][1]
if not attribute_name or attribute_type not in ["Point", "Primitive", "Vertex", "Detail"]:
hou.ui.displayMessage("Invalid input. Please provide valid attribute name and type.")
else:
# 選択ノードと探すアトリビュート名を使用
selected_nodes = hou.selectedNodes()
if not selected_nodes:
hou.ui.displayMessage("No node selected. Please select a node and try again.")
else:
selected_node = selected_nodes[0] # 最初の選択ノード
origin_node = find_attribute_origin(selected_node, attribute_name, attribute_type)
# 結果を表示し、該当ノードを選択
if origin_node:
# 該当ノードを選択状態に設定
origin_node.setSelected(True, clear_all_selected=True)
# ビューアを該当ノードにフォーカス
network_editor = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor) # ネットワークエディタを取得
if network_editor:
network_editor.setCurrentNode(origin_node) # ネットワークエディタを該当ノードにフォーカス
# 結果を表示
hou.ui.displayMessage(f"Attribute '{attribute_name}' ({attribute_type}) was first created at node: {origin_node.path()}")
else:
hou.ui.displayMessage(f"Attribute '{attribute_name}' ({attribute_type}) not found upstream.")
使い方
-
ツールの実行
Shelf Toolに登録して実行します。 -
ポップアップUIで入力
-
Attribute Name: 探したいアトリビュート名(デフォルト:
my_attribute
) -
Attribute Type: アトリビュートのタイプ(デフォルト:
Point
)
-
Attribute Name: 探したいアトリビュート名(デフォルト:
-
結果の確認
- 該当のノードが自動的に選択され、ネットワークビューでフォーカスされます。
- 結果はダイアログにも表示され、対象ノードのパスを確認できます。
発展
このツールは拡張することができ、以下の機能追加が考えられます:
-
アトリビュートの値変更の追跡
指定したアトリビュートがどのノードで値を変更されたのかを追跡する機能です。
これにより、初期生成だけでなく、値が変わる原因を特定できます。def find_attribute_modifications(node, attrib_name, attrib_type): """ 上流をたどり、指定されたアトリビュートの値が変更されたノードを特定する。 """ visited = set() def traverse_upstream(current_node, prev_values=None): if current_node in visited: return [] visited.add(current_node) try: geo = current_node.geometry() except hou.GeometryPermissionError: return [] # 現在のノードのアトリビュート値を取得 attrib_list = { "Point": geo.pointAttribs, "Primitive": geo.primAttribs, "Vertex": geo.vertexAttribs, "Detail": geo.globalAttribs }.get(attrib_type, lambda: [])() target_attrib = next((a for a in attrib_list if a.name() == attrib_name), None) modified_nodes = [] if target_attrib: current_values = geo.attribValue(attrib_name) if attrib_type == "Detail" else None if current_values != prev_values: modified_nodes.append(current_node) prev_values = current_values # 上流ノードを探索 for input_node in current_node.inputs(): modified_nodes.extend(traverse_upstream(input_node, prev_values)) return modified_nodes return traverse_upstream(node)
-
複数アトリビュートの同時検索
複数のアトリビュート名を入力し、一度に検索する機能です。手動で何度も検索する手間を省きます。
複数アトリビュート名をカンマで区切って入力できるようにします。
それぞれのアトリビュート名に対してfind_attribute_origin関数を呼び出して検索します。result = hou.ui.readMultiInput( "Search Multiple Attributes", input_labels=["Attribute Names (comma-separated)", "Attribute Type"], buttons=("OK", "Cancel"), close_choice=1, initial_contents=["attrib1,attrib2,attrib3", "Point"] ) if result[0] == 0: # OKボタンが押された場合 attribute_names = [name.strip() for name in result[1][0].split(",")] attribute_type = result[1][1] for name in attribute_names: origin_node = find_attribute_origin(selected_node, name, attribute_type) if origin_node: print(f"Attribute '{name}' found at node: {origin_node.path()}")
-
ビジュアル強化
ネットワークビュー上で、アトリビュートの流れを可視化します。- 選択ノードのカラーリング
- 対象となるノードに対して、一時的に分かりやすい色(例: 赤やオレンジ)を付けて強調します。これにより、複雑なネットワークの中でも一目で目的のノードが分かります。
-
# ノードのカラーリング node.setColor(hou.Color((1.0, 0.3, 0.0))) # 赤色に設定
- フラグやマーカーを追加
- 特定のノードにアトリビュートを生成・変更した印として、バッジ(フラグ)やカスタム名を付けて目立たせます。
-
ノード名に「_ATTR_SOURCE」のマークが付き、生成・変更点が一目で識別できます。
# ノード名にマークを追加 node.setName(node.name() + "_ATTR_SOURCE", unique_name=True)
- 選択ノードのカラーリング
-
デバッグログの出力
探索過程をログファイルに出力し、後から確認可能にする。
長くなりそうなので別途ページを分けて説明する予定です。
参考資料
Refactoring Houdini Node Network – メンテナンス性の高いノードネットワーク構築のために
Houdini Tutorial: Send debug output (VEX) to the Houdini Console