カスタムOutputProcessorを作ろう
HoudiniSOLARIS の USD ROP ノードには「OutputProcessing」と呼ばれる機能が用意されています。
この OutputProcessing を使用すると、どんなことができるかというと
USD ROP でファイルを保存するタイミングで、プラグインで実装した処理を実行できるようになります。
例として、デフォルトで用意されている機能「Use Relative Path」であれば、
リファレンスのファイルパスを、自動で相対パス化してくれたり、
「Save AllFiles to a Specific Directory」であれば、
OutputDirectory で指定したパスに変換 してくれます。
USD は、複数のファイルで構成されているフォーマットのため
場合によってはパスを書き換えたりなど 様々な処理を入れたくなります。
OutputProcessing を使用しない場合は、一度出力したあとに、別のノードで実行する...といった
HDA を作る必要がありますが、
Python で書ける処理ならば、だいたいのことはこの OutputProcessing を使用して
実装することができます。
Plugin を作成する
まず、プラグインを作成します。
OutputProcessing は Python で実装しますが、その Python ファイルを指定のディレクトリ以下に
作成するとロードできるようになります。
Documents/houdini/VERSION/husdplugins/outputprocessors
以下のフォルダに、適当な名前で Python ファイルを作成します。
import hou
from husd.outputprocessor import OutputProcessor
class SampleOutputProcessor(OutputProcessor):
theParameters = None
@staticmethod
def name():
return "sample_output_processor"
@staticmethod
def displayName():
return "Sample OutputProcessor"
# 以下の記述が必須です: プロセッサクラスを返すモジュールレベルの関数
outputprocessor = SampleOutputProcessor()
def usdOutputProcessor():
return SampleOutputProcessor
中身はこのようにします。
これがプラグインを作成する際の最小構成になります。
name は、このプラグイン自体の名前(ユニークなもの)をスペースなしで、
displayName は、USD ROP の Output Processors の選択メニューで出てくる文字列で、
スペースなどを入れてもOKです。
実装
以上のテンプレに対して、必要な実装をしていきます。
https://www.sidefx.com/ja/docs/houdini/solaris/output.html
実装方法は、上記のページにまとめられていますが、
わかりにくいので1つずつ見ていきましょう。
基本的な流れとしては、処理を実行させたいタイミングに対応する
出力プロセッサメソッド(関数)をオーバーライドする形になります。
対応する関数は以下の 4 つです。
関数 | 機能 |
---|---|
beginSave | USDROP がファイルの書き出しを始めたタイミングで実行。この中だと最も最初に呼ばれる |
processSavePath | USDROP がアセットを保存するディスク上の場所を決定する必要がある時に呼ばれる。 |
processReferencePath | USDROP がアセット(ファイル内のサブレイヤーまたは参照)を指したファイルパスを書き出す必要がある時 |
processLayer | レイヤーファイルをディスクに書き出す直前にコールされます |
プラグインの動作を理解するために、このようなサンプルを作成しました。
2 レイヤーで構成されていて
1 つ目が、Root したに cube1 があるファイルで、Root が DefaultPrim になっているもの。
もう 1 つが、最終的に出力しているレイヤーで、Cube を含むレイヤーを「リファレンス」している
レイヤーです。
beginSave
この beginSave は、その名の通り USDROP が実行されたタイミングで呼ばれます。
def beginSave(self, config_node, config_overrides, lop_node, t, stage_variables):
super().beginSave(config_node, config_overrides, lop_node, t, stage_variables)
最初に呼ばれることから、主にパラメーターの初期化などを行います。
parameters
beginSave での処理を書く前に、理解しておきたいのがパラメーターです。
これは、OutputProcessing を追加した時に USDROP 上に表示される入力用 UI のことで
OutputProcessing で何かしらのパラメーターを設定したい場合は、このパラメーターを定義します。
@staticmethod
def parameters():
group = hou.ParmTemplateGroup()
group.append(hou.IntParmTemplate("sample_output_int_sample", "Int Sample", 1))
SampleOutputProcessor.theParameters = group.asDialogScript()
return SampleOutputProcessor.theParameters
例として、このような関数を追加します。
パラメーターは classmethod で定義し、ParmTemplateGroup.asDialogScript()の値を return します。
設定結果はこのようになります。
OutputProcessor を追加すると、指定したパラメーターが追加されます。
これで、何かしらの値を OutputProcessing 中で使用したい場合、設定可能になります。
ここで追加した値を beginSave 側で初期化します。
def beginSave(self, config_node, config_overrides, lop_node, t, stage_variables):
self.int_value = self.evalConfig("sample_output_int_sample", config_node, config_overrides, t)
evalConfig を使用すると、その時点でのパラメーターに、config_overrides で指定した辞書型
をオーバーした値に評価してくれます。
config_overrides は、デフォルトでは空の辞書型ですが
ここで任意の辞書型を用意して値をオーバーライドすることも可能です。
初期化したパラメーターはクラス変数としてセットされたので、以降の関数でも
使用することができます。
もう1つ重要な点は、この関数の基底クラス実装を必ず呼び出す必要があります。
def beginSave(self, config_node, config_overrides, lop_node, t, stage_variables):
self.int_value = self.evalConfig("sample_output_int_sample", config_node, over, t)
# 基底クラスをコールすることで、
# 処理系メソッドで self.config_node self.lop_node self.t が使用できるようになる
super().beginSave(config_node, config_overrides, lop_node, t, stage_variables)
このように super().beginSave(~~~)で指定すれば OK です。
これを呼び出すことで、 config_node (地震の USDROP ノード) lop_node t のパラメーターを
self で呼び出せるようになります。
processSavePath
processSavePath は、レイヤーを保存する先のファイルパスを決定するときに呼ばれます。
return で書き出し先のフォルダを返すようにすればよいので
パスを相対化したり、何かしらの値で定義してあった値を置換したりといったことを
この processSavePath で実装することができます。
processReferencePath
processReferencePath は、リファレンスやサブレイヤーをしているレイヤーを
書き出す際に実行されます。
def processReferencePath(self, asset_path, referencing_layer_path, asset_is_layer):
今回の例だと、LOP 内のネットワークでレイヤーを作成しリファレンスをしていますが、
この場合 Cube のレイヤー出力前にこの関数が呼ばれ、
asset_path > cube のレイヤー
referencing_layer_path > リファレンスをしている(USDROP で出力しようとしている)レイヤー
asset_is_layer > asset_path のアセットが USD ファイルかどうか
が入ってきます。
これを利用して、たとえばリファレンスしているパスを相対パス化したり
データの収集>パス変更などを行うことができます。
def processReferencePath(self, asset_path, referencing_layer_path, asset_is_layer):
"""
レンダーノードがアセット(ファイル内のサブレイヤーまたは参照)を指したファイルパスを書き出す必要がある時にコールされます
"""
return hou.text.relpath(asset_path, referencing_layer_path)
例として、リファレンスのパスを読み込んでいるレイヤーからの相対に書き換えた例。
processReferencePath の return では、リファレンスのパス(USD にセットするパス)を
返します。
この結果、リファレンスのパスは相対パスになりました。
今回は 1 回だけ processReferencePath が実行されましたが、この関数はリファレンスの数だけ
実行されます。
processLayer
最後は ProcessLayer。
この関数は、USD ファイルに書き込む直前の SdfLayer を引数で受け取ることができます。
つまりは、書き込む直前に、なんでもできるのが ProcessLayer です。
この processLayer は、エクスポートしたい LOP のレイヤーの数だけ実行されます。
def processLayer(self, layer):
print(layer.ExportToString())
# 変更があったらTrueにする
return False