3
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?

Houdini HDA開発入門:PythonでKey-Value Dictionaryを自動生成し、外部モジュールをPythonModule経由で連携する

Last updated at Posted at 2025-12-14

今回のゴール

  • HDA で使う 外部 Python モジュール を、安全にリロードしながら開発できるようにする
  • Detail の dict 型アトリビュート から、自動で Key-Value Dictionary パラメータ を生成・削除できるようにする

最終的には、

  • HDA 生成時に自動で Key-Value Dictionary パラメータが追加される
  • 辞書型アトリビュートを更新したら、ボタン 1 つでパラメータ側に反映できる

というワークフローを目指します。
(コードの更新をするたびPython Moduleにコピペするが面倒なのでなんとかしたい)


今回特に紹介したいPythonコード

全てのdetailアトリビュート名を取得

ポイント: intrinsicValue は他にもいろいろ取得できる。

全てのpointグループなど とても便利です。

attr_name_list :list[str] = geo.intrinsicValue('globalattributes')

Key-Value-Dictionaryのパラメータ作成

DataParmTemplate作成後に

setDataParmType(hou.dataParmType.KeyValueDictionary)のメソッドで指定します。

data_parm = hou.DataParmTemplate(
                name=parm_name,
                label=label,
                num_components=1,
            )
data_parm.setDataParmType(hou.dataParmType.KeyValueDictionary)

全体のフローイメージ

まずは、今回作る仕組みの全体像です。
create_HDA.jpg

image.png

  • 上側:外部モジュールと HDA の連携の流れ
  • 下側:Detail の dict アトリビュートから Key-Value Dictionary パラメータを自動生成した結果

外部モジュールのパス設定

Houdini から外部モジュールを import できるように、packages ファイルでパスを設定します。

{
  "enable": true,
  "env": [
    {
      "HOUDINI_PATH": "D:/Development/Houdini_Tools"
    },
    {
      "COMMON": "D:/Development/Houdini_Tools/packages/Houdini 21"
    },
    {
      "HOUDINI_PYTHON_PANEL_PATH": "$COMMON/python_panel"
    },
    {
      "HOUDINI_TOOLBAR_PATH" : "$COMMON/toolbar"
    },
    {
      "HOUDINI_OTLSCAN_PATH":"$COMMON/hda"
    },
    {
      "PYTHONPATH":"$COMMON/scripts/python/"
    },
    {
      "HOUDINI_APEXGRAPH_PATH": "D:/Development/Houdini_Tools/apexgraph"
    },
    {
      "EDITOR":"$LOCALAPPDATA/Programs/Microsoft VS Code/Code.exe"
    },
    {
      "HOUDINI_EXTERNAL_HELP_BROWSER":"https://www.sidefx.com/docs/houdini21.0/"
    }
  ]
}

ここではいろいろな環境変数を定義していますが、今回の主役は PYTHONPATH です。

"PYTHONPATH":"モジュールパス"

PYTHONPATH は、Houdini(というより Python 全体)が

import するモジュールを探しに行く場所」を示す環境変数です。

そのため、

自分のモジュールを置いているディレクトリを PYTHONPATH に追加しておく

ことが重要になります。

想定ディレクトリ構成

例として、次のようなパスを用意します。

  1. HDA で使うモジュール用のディレクトリを作る

    例:D:/Development/Houdini_Tools/scripts/python/hda_pkg

  2. そのディレクトリを PYTHONPATH に含めるように設定する。

  3. ディレクトリ内に __init__.py を置いてパッケージとして扱えるようにする。


reload 処理付きパッケージを用意する

開発中にモジュールを書き換えたとき、Houdini を再起動せずに変更を反映させたいので、

__init__.pyreload 関数を用意しておきます。

#__init__.py
import importlib
import sys

def reload():
    prefix = __name__ + "."
    for name, module in list(sys.modules.items()):
        if name == __name__ or name.startswith(prefix):
            importlib.reload(module)

この reload() を呼ぶことで、

  • パッケージ本体
  • その配下のモジュール

まとめて再読み込みできます。


動作確認用モジュール test_math.py

まずは挙動確認のため、簡単な計算クラスを作ります。

#test_math.py
class Math:
    def __init__(self, a: float, b: float):
        self.a = a
        self.b = b

    def sum(self) -> float:
        c = self.a + self.b
        return c

    def multiply(self) -> float:
        c = self.a * self.b
        return c

    def divide(self) -> float:
        c = self.a / self.b
        return c

スクリーンショット 2025-12-13 183102.png


外部エディタからテスト実行する

Windows メニューの Python Source Editor から外部エディタを開きます。

開いたエディタ上で、次のようなコードを実行して、

  • PYTHONPATH の設定が効いているか
  • reload が期待どおり動いているか

を確認します。

import hda_pkg
from hda_pkg.test_math import Math

hda_pkg.reload()

print(Math(2, 3).sum())
print(Math(3, 3).divide())

確認ポイント

  • PYTHONPATH に設定したディレクトリが正しく参照されているか
  • test_math.py に関数やメソッドを追加・削除したあと、hda_pkg.reload() で変更が反映されるか

を試して、パス設定と reload 処理が正しく機能しているかをチェックします。


HDA 内での Reload 確認フロー

HDA の PythonModule を経由して挙動を確認する場合の、現状のフローです。

Apply → Reload All Files → Apply → 実行ボタン → Apply

現状はやや手順が多く、ここは今後改善の余地ありです。


Detail クラスに dict 型アトリビュートを作る

次に、Key-Value Dictionary パラメータの元になる dict 型の Detail アトリビュートを作ります。

HDAの中でも外でもどこに作っても良いです。

スクリーンショット 2025-12-14 154411.png
スクリーンショット 2025-12-14 153837.png

// Run Over: Detail (only once)

dict animal;
animal["cat"] = "male";
animal["dog"] = "female";

setdetailattrib(0, "dict_animal", animal);

dict fruit;
fruit["apple"]  = 2;
fruit["banana"] = 3;
fruit["cherry"] = 1;

setdetailattrib(0, "dict_fruit", fruit);
  • dict_animaldict_fruit という 2 つの Detail dict アトリビュートを作成
  • これらを元に、後で HDA パラメータとして Key-Value Dictionary を自動生成します

Key-Value Dictionary を自動生成する仕組み

Key-Value Dictionary とは?

Houdini のパラメータ上で、辞書型データをキー・バリューの組として管理できるパラメータタイプです。

スクリーンショット 2025-12-14 153914.png

ここでは、Detail の dict アトリビュートをスキャンして、対応する Key-Value Dictionary パラメータを自動で作る仕組みを整えます。


パッケージのディレクトリ構造(暫定)

現時点では、hda_pkg は次のような構成を想定しています。

.
└── hda_pkg
   ├── utils
   │   └── hda_base.py
   ├── __init__.py
   └── parameter.py

今後の運用の中で、必要に応じて構成を見直していきます。


HDA の基底クラス hda_base.py

HDA 全般で共通して使いたい処理をまとめるための基底クラスです。


import hou

class HDABase:
    """
    hda_pkgの基底クラス 
    """
    def __init__(self,kwargs):
        self.this_node :hou.SopNode =kwargs['node']
        self.this_node_geo :hou.Geometry = self.this_node.geometry()
        

    def get_current_node(self)->hou.SopNode :
        """ 
        このHDAのノードを返す
        """
        return self.this_node
    
    def get_globalattributes_datatype(self) ->dict[str,hou.attribData] :
        """ 
        グローバルアトリビュート名とdataTypeの辞書を返す
        """
        
        attr_name_list :list[str] = self.this_node_geo.intrinsicValue('globalattributes')
        
        datatype_dict : dict[str,hou.attribData] ={}
        
        for attr_name in attr_name_list:
            
            attr = self.this_node_geo.findGlobalAttrib(attr_name)
            datatype_dict[attr_name]  = attr.dataType()
            
        return datatype_dict

ポイント:

  • get_globalattributes_datatype() で、Global Attribute 名とその attribData 型の対応表を dict で返す
  • 後で「Dict 型のアトリビュートだけを拾う」処理に使います

パラメータ生成用の基底クラス parametar.py

次に、パラメータ生成ロジックをまとめたクラスを用意します。

import hou
from hda_pkg.utils.hda_base import HDABase

class Parameter(HDABase): 
    """
    パラメータの基底クラス
    """
    
    def __init__(self,kwargs):
        super().__init__(kwargs)  #  親の初期化を呼ぶ
        
    def create_kv_parms(self)->None:
        """
        key_value_dictionaryの作成
        """
        
        datatype_dict = self.get_globalattributes_datatype()

        dict_names: list[str] = [
            name for name, dt in datatype_dict.items()
            if dt == hou.attribData.Dict
        ]

        ptg = self.this_node.parmTemplateGroup()

        # dict attribute名 -> 作るparm名 の対応
        dict_to_parm = {}

        for dict_name in dict_names:
            parm_name = f"kv_{dict_name}"

            # 既に同名パラメータがあるならスキップ(または置き換え)
            if ptg.find(parm_name) is not None:
                continue

            label = dict_name[:1].upper() + dict_name[1:]

            data_parm = hou.DataParmTemplate(
                name=parm_name,
                label=label,
                num_components=1,
            )
            data_parm.setDataParmType(hou.dataParmType.KeyValueDictionary)

            ptg.append(data_parm)
            dict_to_parm[dict_name] = parm_name

        self.this_node.setParmTemplateGroup(ptg)

        # ここから値の同期
        for dict_name, parm_name in dict_to_parm.items():

            parm = self.this_node.parm(parm_name)
            if parm is None:
                continue

            # detail(dict) attribute の値を取得
            try:
                value = self.this_node_geo.attribValue(self.this_node_geo.findGlobalAttrib(dict_name))
            except Exception:
                value = {}

            # KeyValueDictionary は {str: str} しか入らないので変換
            kv_value = {str(k): str(v) for k, v in (value or {}).items()}

            parm.set(kv_value)
            
    def delete_created_kv_parms(self)->None :
        """
        key_value_dictionaryの削除
        """
        
        ptg =self.this_node.parmTemplateGroup()

        # まず削除対象の名前を集める(走査しながら消すと壊れやすいのでリスト化)
        targets = []
        for parm_tmpl in ptg.entries():
            # FolderParmTemplate も entries() に混ざるので name() が取れるものだけ
            try:
                name = parm_tmpl.name()
            except Exception:
                continue

            if not name.startswith("kv_"):
                continue

            # KeyValueDictionary の DataParmTemplate だけを消したい場合は型で絞る
            if isinstance(parm_tmpl, hou.DataParmTemplate):
                if parm_tmpl.dataParmType() == hou.dataParmType.KeyValueDictionary:
                    targets.append(name)

        # 削除
        for name in targets:
            ptg.remove(ptg.find(name))

        self.this_node.setParmTemplateGroup(ptg)

やっていること:

  • Global Attribute のうち Dict 型 (hou.attribData.Dict) だけ を抽出
  • kv_ プレフィックス付きの Key-Value Dictionary パラメータを自動生成
  • Detail dict の中身を {str: str} に変換してパラメータに流し込む
  • 後から消したくなったときのために、kv_ で始まる Key-Value Dictionary パラメータだけを削除するメソッドも用意

「生成」と「削除」を 1 クラスにまとめておくことで、HDA のボタンコールバックからシンプルに呼び出せます。


HDA の Python Module

作成したクラスを HDA 側から呼び出すための Python Module です。

import hda_pkg
from hda_pkg.parameter import Parameter

hda_pkg.reload()

def on_create_button_pressed(kwargs):
    Parameter(kwargs).create_kv_parms()
    
def on_delete_button_pressed(kwargs):
    Parameter(kwargs).delete_created_kv_parms()

ポイント:

  • 冒頭で hda_pkg.reload() を呼んでおくことで、外部モジュールの更新を HDA 側にすぐ反映
  • HDA からは on_create_button_pressed / on_delete_button_pressed という 2 つの関数だけを意識すればよい構成

HDA の onCreated

HDA 作成時に、自動で Key-Value Dictionary パラメータを生成するための設定です。

スクリーンショット 2025-12-14 141417.png

kwargs['node'].hdaModule().on_create_button_pressed(kwargs)

HDA の On Created スクリプトにこの 1 行を書いておくことで、

HDA 生成と同時に create_kv_parms() が実行されます。


追加と削除用のボタン

HDA のパラメータに、手動で 追加 / 削除 するためのボタンを用意します。

スクリーンショット 2025-12-14 141643.png

hou.phm().on_create_button_pressed(kwargs)
hou.phm().on_delete_button_pressed(kwargs)
  • 各ボタンの Callback Script に上記のコードを設定
  • これにより、HDA 作成後でも Detail dict を更新したタイミングでパラメータを再生成できます

まとめ:このワークフローでできること

この一連の仕組みにより、次のような流れが実現できます。

  • HDA の中に 辞書型の Detail アトリビュート を追加する
  • HDA 作成時、あるいはボタン押下時に、対応する Key-Value Dictionary パラメータ が自動で生成・同期される
  • 外部モジュール側のロジックも reload() を通じて安全に更新できる

結果として、

  • 「ジオメトリ上の辞書」と「HDA パラメータ上の辞書」をセットで扱える
  • ディティールアトリビュートにあるデータをわざわざパラメータ化する手間を省き、データを一元管理することができます

今後は、生成タイミングや UI 配置、Reload フローの簡略化など、運用しながらブラッシュアップしていく余地があります。

3
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
3
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?