0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

アトリビュートが付与されたノードを一撃で特定するツールの開発

Posted at

必要になった経緯

 他人から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
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?