🟠

Houdini: FontSOP機能追加 - はじめてのPython

2024/12/01に公開

もうアドベントカレンダーの季節とか嘘でしょう…?

気を取り直して皆さんいかがお過ごしでしょう。僕は今年もHoudini三昧の毎日でした。

今回はFontSOPに機能追加を施す方法についてお話をしたいと思います。

本記事はHoudini Apprentice アドベントカレンダー2024 1日目の記事です。Apprenticeということでできるだけシンプルな題材を取り上げ、コードについても手厚めに解説しています。

ちなみに今回から技術ブログはZennに移行しました!よろしくお願いします。

データ配布

HDAダウンロード

ツール制作の背景とその特徴

FontSOPはテストジオメトリの作成や表現そのものにもよく用いられますが、フォントを選ぶ際、メニューリストから毎回選択しなければならないことがUIとして不便に思っていました。

そこで任意の文字列からプレビュー用の文字列ジオメトリを一括で生成し、使用したい文字列をクリックすることで好みのフォントがセットされたFontSOPに切り替わるツールを作成しました。

こちらは続くHoudiniアドベントカレンダー19日目の記事でご紹介する予定なのですが、もっとシンプルに次のフォント・前のフォントを選べるボタンUIを作るだけでも高いUXになるのではと思い作成してみました。

振り返ると、本ツールはHoudini Python初心者向けに良い題材だと思われたので、Apprenticeのアドベントカレンダーでご紹介しようと思った次第です。

動作は下記動画をご参考ください。
動作確認用動画

HoudiniとPython

HoudiniではPythonを様々な用途で使用可能です。シェルフツールを作って作業を効率化したり、ノードを生成したり、もちろんアトリビュートへのアクセスも可能です。(VEXを用いた並列処理で行うかどうかを考慮する必要はありますが)

今回はノードのパラメータを操作するという比較的シンプルでわかりやすい使用方法をご紹介しましょう。

ツール構成の説明

ここから実際のネットワークを見ながらツール構成を順にご説明します。本記事は初心者向けにPythonコードの説明や手順をメインに行いますが、HDAの作成方法には触れません。

パラメータ

最初に最終的なパラメータを確認しておきましょう。FontSOPと比較していただければわかりますが、単純に<ボタン>ボタンを追加しただけです。

パラメータ

これから僕らが作るツールではボタンを押すたびにメニューリストから「次へ」「前へ」と順にフォントを切り替える機能を追加すればいいというわけですね。

ツールのネットワーク

まずはAllow Editing of Contentsを実行せずHDAの中にダイブしてみましょう。FontSOPOutputSOPしかありませんね。OutputSOPは何もしないノード(出力されるジオメトリを決定する機能だけを有しているノード)ですので、このHDAの中身は実質的にFontSOPだけしか入っていないということになります。
Network

FontSOPのパラメータをすべて露出

皆さんがこの手順を実際にやってみたい場合はHDA作成後、Edit Operator Type PropertiesウィンドウのParametersタブFontSOPを丸ごとExisting Parametersにドラッグ・アンド・ドロップしてあげましょう。

これでFontSOPのパラメータが全てHDAで露出されることとなります。

前へ、次へボタンの作成

fontパラメータの前と後ろにprevパラメータとnextパラメータを作成します。パラメータの設定は下記スクリーンショットをご参考下さい。

prevパラメータ

nextパラメータ

ここで重要となるのがCallback Scriptの項目です。下記のように設定されていることを確認して下さい。

パラメータ Callback Script
prev hou.phm().change(kwargs, -1)
next hou.phm().change(kwargs, 1)

そもそもCallback Scriptとはなにかという簡単な説明をすると、ボタンが押されたときに実行されるプログラムのことで、hou.phm()の部分で「HDAに埋め込まれたスクリプト」を指し示し、changeで「change関数を実行してね」とHoudiniに伝えています。

そして今回の一番のポイントが引数であるkwargs, -1kwargs, 1の部分です。ここが異なっているおかげでchange関数の挙動をコントロールできる(前に、次にを切り替えられる)というわけですね。

change関数のコード解説

Edit Operator Type PropertiesウィンドウのScriptタブPythonModuleを作成し、ここにコールバック関数であるchangeを定義しています。

HDA に Python モジュールを登録する手順については下記BornDigitalさんの記事にまとまっていたためご参考下さい。

HDA に Python モジュールを登録する

続けてコードを見ていきましょう。説明のためコメントを多く入れていますが、コメントを外すととても短いことに驚くかと思います。はじめてPythonを書くのであれば、チャレンジしやすい分量ですね。

change関数の実際のコード

def change(kwargs, action):
    """
    FontSOPのfileパラメータのメニュー項目を切り替える関数

    Args:
        kwargs (dict): ノード情報を含む辞書
        action (int): メニュー項目のインデックスを変更する値
    """
    # ノードのfileパラメータを取得
    parm_file = kwargs["node"].parm("file")

    # 現在選択されているフォントを取得
    current_label = parm_file.eval()

    # メニュー項目から重複とセパレータを除外してソート
    labels = sorted({l for l in parm_file.menuLabels() if l != "_separator_"})

    # 循環的にインデックスを更新してフォントを切り替え
    next_index = (labels.index(current_label) + action) % len(labels)
    parm_file.set(labels[next_index])

change関数からコメントを外したコード

def change(kwargs, action):
    parm_file = kwargs["node"].parm("file")
    current_label = parm_file.eval()
    labels = sorted({l for l in parm_file.menuLabels() if l != "_separator_"})
    next_index = (labels.index(current_label) + action) % len(labels)
    parm_file.set(labels[next_index])

なんと6行ですね。では解説を続けていきましょう。

def change(kwargs, action):ではchange関数の定義をしています。引数にkwargsactionをとる関数だよということですね。

parm_file = kwargs["node"].parm("file")の部分ではノードのfileパラメータを取得しています。ボタンを押されたときにkwargsという情報がこの関数に渡ってくるのですが、これは辞書型のデータで、インタラクション発生時の多くの情報がここでわかるようになっています。

kwargs["node"]がHDAそのもの(ノード)を表していて、そこからパラメータを探すことができるわけですね。(今回はfileパラメータを取得しました)

current_label = parm_file.eval()では現在選択されているフォントを取得しています。evalというメソッドを使用するとパラメータが持つデータを取得することができます。

labels = sorted({l for l in parm_file.menuLabels() if l != "_separator_"})の部分ですが、少々ややこしいですね。Pythonに慣れていないと入れ子構造のワンライナーには最初戸惑いますが、ここで行っている処理は下記をまとめたものです。

  1. fileパラメータのメニューリストから_separator_を取り除く
  2. メニューリストから重複を取り除く
  3. メニューリストをアルファベット順に並び替える

これでフォントを順番に切り替える下地ができました。ちなみに上記では「メニューリスト」と簡単に記載していますが、正確にはデータ型を変更する処理が入っています。興味がある方は調べてみて下さい。

next_index = (labels.index(current_label) + action) % len(labels)の部分でメニューリストにアクセスするインデックスを取得しています。ポイントは+ actionのところで、押されたボタンによって(つまり引数actionの値が+1なのか-1なのかによって)インクリメントされるのかデクリメントされるのかが決定されます。

また% len(labels)の処理も気をつけたいポイントです。これを行わないとメニューリストを循環的にアクセスできません。どんどん>ボタンを押し続けていくとメニューリストの最後を超えてアクセスしてしまうという問題が起こるわけです。

最後にparm_file.set(labels[next_index])の部分でパラメータを更新しています。

こんな短いコードでもひとつひとつ見ていくと大変かもしれませんが、printデバッグなどを利用しながら現在どんな処理が行われているかを順を追って確認すれば、決して理解できないものではないでしょう。

まとめ

最後まで読んでくださった方、ありがとうございます。ビルトインノードであっても「不便だな」と思ったところは改善していくと、より理解が深まったり多角的な視点を持つ事ができるようになったりします。

もう一歩難易度の高いツールに関しては12/19にご紹介するとします。

ではでは、来年も素敵なHoudiniライフを!

開発環境

  • Windows10
  • Houdini 20.0.688

参考

フォントプレビューのベストプラクティス

Discussion