dicepastoso
@dicepastoso

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

アトリビュートがどこで付与されたかを一撃で探るツールの開発

必要になった経緯

 他人からhipファイルを渡された時に複雑なノードネットワークの中でアトリビュートの起点や変更を知りたい時に手動だと大変な場合があります。ノードの右下に付与されたアトリビュート名が表示されるのでそれを目印に探すことは可能ですが、大規模ネットワークだと少々手間です。
 また、デバッグや最適化をする際にもすぐに起点、変更ポイントに行きたくても時間がかかります。
このような課題を解決するため、 「アトリビュートが付与されたノードを一撃で特定するツール」 を開発しました。

アトリビュートがどこで付与されたかを探る手段

Houdiniの標準機能では、以下の方法で確認することができます:

  1. 手動で上流ノードを一つずつ確認

    • ノードのパラメータやGeometry情報をチェックします。
    • ネットワークが大きい場合、非常に手間がかかります。
  2. Geometry Spreadsheetを利用

    • アトリビュートの存在や値を直接確認できますが、どのノードで付与されたかを知るには上流ノードを順番に選択し、スプレッドシートでアトリビュートの有無や値の変化を確認する必要があります。
  3. Attribute CreateやWrangleノードを探す

    • アトリビュートは通常、以下のようなノードで作成されます
      • Attribute Create
      • Attribute Wrangle
      • Attribute Expression
      • VOPノード(Attribute VOP)
    • ネットワーク内でこれらのノードを探し、特定のアトリビュートが操作されている箇所を確認しますが、手動では時間がかかります。
  4. 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

2024-12-18_23h21_44.png

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.")

使い方

  1. ツールの実行
    Shelf Toolに登録して実行します。

  2. ポップアップUIで入力

    • Attribute Name: 探したいアトリビュート名(デフォルト: my_attribute
    • Attribute Type: アトリビュートのタイプ(デフォルト: Point
  3. 結果の確認

    • 該当のノードが自動的に選択され、ネットワークビューでフォーカスされます。
    • 結果はダイアログにも表示され、対象ノードのパスを確認できます。

発展

このツールは拡張することができ、以下の機能追加が考えられます:

  1. アトリビュートの値変更の追跡
    指定したアトリビュートがどのノードで値を変更されたのかを追跡する機能です。
    これにより、初期生成だけでなく、値が変わる原因を特定できます。

    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)
    
  2. 複数アトリビュートの同時検索
    複数のアトリビュート名を入力し、一度に検索する機能です。手動で何度も検索する手間を省きます。
    複数アトリビュート名をカンマで区切って入力できるようにします。
    それぞれのアトリビュート名に対して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()}")
    
    
  3. ビジュアル強化
    ネットワークビュー上で、アトリビュートの流れを可視化します。

    • 選択ノードのカラーリング
      • 対象となるノードに対して、一時的に分かりやすい色(例: 赤やオレンジ)を付けて強調します。これにより、複雑なネットワークの中でも一目で目的のノードが分かります。
      •  # ノードのカラーリング
                     node.setColor(hou.Color((1.0, 0.3, 0.0)))  # 赤色に設定       
        
    • フラグやマーカーを追加
      • 特定のノードにアトリビュートを生成・変更した印として、バッジ(フラグ)やカスタム名を付けて目立たせます。
      •     # ノード名にマークを追加
            node.setName(node.name() + "_ATTR_SOURCE", unique_name=True)
        
        
        ノード名に「_ATTR_SOURCE」のマークが付き、生成・変更点が一目で識別できます。
  4. デバッグログの出力
    探索過程をログファイルに出力し、後から確認可能にする。
    長くなりそうなので別途ページを分けて説明する予定です。


参考資料

Refactoring Houdini Node Network – メンテナンス性の高いノードネットワーク構築のために

Houdini でプログラミング:アトリビュート

そんなことよりHoudiniやろうぜ!

Houdini Tutorial: Send debug output (VEX) to the Houdini Console

サルにもわかる Houdini: Using Python in Houdini

Houdini x Python

0

これはこの開発したものに対する 何の意見交換なのでしょうか?(そもそもこの内容なら記事でよかったのではないでしょうか……?

0Like

Your answer might help someone💌