On this page |
概要 ¶
熟練ユーザは、Pythonプラグインを記述することで、Scene Import LOPが特定のHoudiniオブジェクトノードタイプをUSDに変換する方法をカスタマイズすることができます。 これは、特にカスタムノードタイプ(例えば、プロプライエタリなレンダラー関係のライトタイプやカメラタイプ)の変換に役立ちます。 また、一般のオブジェクトノードタイプの変換でも独自のデータまたはワークフローを制御できるように、または、USD側で独自のデータを生成できるように修正したり機能追加するのにも役立ちます。
Note
変換プラグインの実装には、Pixar社のUSD Python APIに熟知している必要があります。
-
Houdiniは
HOUDINIPATH/husdplugins/objtranslators/
内でシーンインポートプラグインを検索します。$HOUDINI_HUSDPLUGINS_PATH
環境変数を設定することで、HoudiniがUSD関連のプラグインを検索する場所をカスタマイズすることができます。 -
そのパスから見つかるようにPythonソースコード(
.py
)ファイルを作成します(例えば、$HOUDINI_USER_PREF_DIR/husdplugins/objtranslators/my_translator.py
)。以下のテンプレートの内容をコピーして、プラグインファイルにペーストしてください。
-
プラグインファイルには最低でも1個のトランスレータクラス(
husd.objtranslator.Translator
からサブクラス化)、単一のmanager
引数を受け取るregisterTranslators()
という名前の関数を用意し、そのregisterTranslators()
関数を使ってそのプラグインファイル内にあるトランスレータクラスを登録する必要があります(プラグインファイルテンプレートを参照)。 -
単一の
.py
ファイル内に複数のトランスレータを定義して登録することができます(これによって、起動時間が改善されます)。とはいえ、各プラグインを別々のファイルに分けたほうが個々にプラグインをパス内に移したり出したりできてる柔軟性があります。 -
パス内で先に見つかったファイルは、後で見つかった同じ名前のファイルを上書きします。
-
Scene Import LOPがプラグインクラスに登録されているタイプ名のオブジェクトノードを検出すると、そのノードインスタンスをクラスの単一引数に渡してそのクラスをインスタンス化します。
Translate.__init__()
のデフォルトの実装では、そのノードインスタンスをself._node
として保存するので、他のメソッドでそのアトリビュートを使用することで、変換されるオブジェクトノードにアクセスすることができます。その後、Scene Import LOPは、そのオブジェクトの様々なメソッドをコールして、現行オブジェクトノードを変換すべきかどうか、どの種類のUSD Primに変換すべきかなどをチェックします。各プラグインメソッドの詳細は、以下のファイルテンプレートを参照してください。
プラグインファイルテンプレート ¶
Pythonファイル内に以下の骨組みをペーストします:
from husd.objtranslator import Translator from pxr import Sdf, Usd # トランスレータクラスは、husd.objtranslator.Translatorをサブクラス化する必要があります。 # おそらく変換するノードタイプに基づいてトランスレータクラスの名前を付けたいことでしょう。 # 例えば、StudioEnvLightTranslatorです。 class MyCustomTranslator(Translator): def shouldTranslateNode(self): # self._nodeを使用することで、変換される現行オブジェクトノードを表現したhou.OpNodeインスタンスにアクセスすることができます。 # このノードをUSDに変換したい場合はTrueを、そうでない場合はFalseを返します。 # 例えば、指定したタイプのノードがまだ変換されていない場合、 # または、ノードを"非表示にする"パラメータがノードに用意されていて、その"非表示にする"パラメータが有効になっているオブジェクトを変換したくない場合には、 # Falseを使用します。 return True def primType(self): # 変換されるオブジェクトノードに呼応して作成するUSD Primタイプの名前(例えば、"Xform")を含んだ文字列を返します。 return "" def sopNodeFlag(self): # オブジェクトノードの中にSOPネットワークが含まれている場合、このメソッドは、そのオブジェクトのジオメトリ出力として使用したい # ノード(例えば、ディスプレイフラグが付いたノード、または、レンダーフラグが付いたノード)をScene Import LOPに伝えます。 # self.SOP_RENDER_FLAGまたはself.SOP_DISPLAY_FLAGのどちらかを返してください。 return self.SOP_RENDER_FLAG def referencedNodePaths(self): # self._nodeを使用することで、変換される現行オブジェクトノードを表現したhou.OpNodeインスタンスにアクセスすることができます。 # これは、このノードが参照/依存しているすべての外部ノードを表現した辞書を返してください。 # # その辞書内の各キーは、外部ノードとの関係を示した任意のキーワードです。 # そのキーに呼応した値は、参照されるノードのノードパスです。 # # (ノードパスの代わりにUSDパスが入った辞書が以下のpopulatePrim()メソッドに渡されるので、 # キーワードはそれら2つのメソッド間でのみ意味が通じて/一致するようにすれば良いです。 # # 例えば、ポータルジオメトリとして使用するGeoオブジェクトのノードパスをユーザが入力できるパラメータが環境ライトノードにある場合、 # 以下のように返すことができます: # return {"portal": self._node.evalParm("env_portal")} return {} def populatePrim(self, prim, referenced_node_prim_paths, force_active): # self._nodeを使用することで、変換される現行オブジェクトノードを表現したhou.OpNodeインスタンスにアクセスすることができます。 # このメソッドにUSD Primを渡すと、変換されるオブジェクトノードからすべての必要な情報でそのUSD Primのプロパティが設定されます。 # # prim.CreateAttribute()やprim.CreatePrimvar()などのUSD APIを使用することで、アトリビュートを作成して設定することができます。 # # self.populateAttr()ヘルパー関数を使用して、オブジェクトノードのパラメータの情報をPrimのプロパティにコピーします。 # このヘルパー関数は、時間依存とタイムサンプルに関係する記録を自動的に世話します。 # # このメソッドは、以下の引数を使ってコールします: # # prim: 変換されるオブジェクトノードに呼応するUSD Prim。 # そのオリジナルオブジェクトノードと同等のUSDとして動作できるように、このPrim上にプロパティを作成して値を埋めてください。 # # referenced_node_prim_paths: これは、上記のreferencedNodePaths()から返された辞書の値のノードパスを、 # 変換されるUSDステージの呼応するSdfパスに置換した辞書です。 # # force_active: ユーザがこれをTrueにすると、オリジナルのオブジェクトノードがシーン内で非表示だったとしても、 # その変換されたPrimが常に"アクティブ"として作成されるようにしてください。 pass # プラグインファイルには必ず登録関数を入れなければなりません。これは、Houdiniがプラグインファイルを読み込んだ時に検索される関数です。 def registerTranslators(manager): # 1つ目の引数は、Houdiniオブジェクトノードタイプの名前です。 # 2つ目の引数は、そのノードタイプを変換するクラスです。 manager.registerTranslator('node_type_name', MyCustomTranslator)
How to ¶
あなたのスタジオのstudio::domelight
という名前の内製ドームライトオブジェクトを変換したいとします。
では、プラグインファイルの作成から始めましょう。
今のところは、現行ユーザのみが利用できるようにユーザプリファレンス内にファイルを作成します($HOUDINI_USER_PREF_DIR/husdplugins/objtranslators
)。
プラグインが完成したら、部署内のアーティスト全員、または、スタジオ内のユーザ全員がそのファイルを取得できるようにHoudiniパス内のどこかにそのファイルを移すと良いでしょう(スタジオでは、Houdiniパスに例えばスタジオレベルの$HSITE
、ショットレベルの$JOB
などの共有ネットワークの場所が入るようにカスタマイズすることが多いですが、それは今回のサンプルでは考慮しません)。
-
テキストエディタで、
$HOUDINI_USER_PREF_DIR/husdplugins/objtranslators/studio_domelight.py
プラグインファイルを作成します。(単一ファイル内に複数のトランスレータを実装することができます。その場合、特定のトランスレータの名前よりもそれらのトランスレータに共通する名前に基づいてファイル名を付けると良いでしょう。)
-
上記のテンプレートコンテンツをコピーして、そのファイルにペーストします。
-
変換するオブジェクトタイプ(s)がわかるようにクラスの名前を変更します。例えば、
StudioDomeLightTranslator
です。 -
ファイルの下部で、
manager.registerTranslator()
コールを編集します。-
1つ目の引数をクラスが変換するオブジェクトノードタイプの内部名に変更します。この場合では
studio::domelight
です。 -
2つ目の引数のクラス名を上記で使用したクラス名(
StudioDomeLightTranslator
)に変更します。
def registerTranslators(manager): # 1つ目の引数は、Houdiniオブジェクトノードタイプの名前です。 # 2つ目の引数は、そのノードタイプを変換するクラスです。 manager.registerTranslator('studio::domelight', StudioDomeLightTranslator)
-
-
そのトランスレータクラスに
shouldTranslateNode()
メソッドを実装します。たいていの場合、これは単に
True
を返します。 しかし、ドームライトタイプにlight_enable
パラメータがあって、そのパラメータが有効な場合にのみドームライトをUSDに変換したいとしましょう。self._node
を使用することで、そのノードを表現したhou.OpNodeオブジェクトにアクセスすることができます。def shouldTranslateNode(self): # これはチェックマークパラメータなので、eval()は0か1を返します。 return self._node.parm("light_enable").eval()
-
トランスレータクラスに
primType()
を実装します。これは、作成するUSD Primタイプの名前を含んだ 文字列 を必ず返してください。 どのトランスレータも必ずこのメソッドを実装しなければなりません 。def primType(self): return "DomeLight"
-
デフォルトでは、SOPネットワークを含んだオブジェクトノード(例えば、Geoノード)の場合、トランスレータは、 レンダーフラグ が付いたノードの出力を使用します。何かしらの理由で代わりに ディスプレイフラグ を使用したいのであれば、
sopNodeFlag()
メソッドを上書きすることができますが、通常ではこれは不要です。ドームライトの場合、このメソッドは 上書きしないことにします 。 しかし、前例を挙げると、
hairgen
のトランスレータはディスプレイジオメトリを使用しています。 その理由は、そのノードの“レンダー”ジオメトリはプロシージャルシェーダ用の入力として用意されていて、USD内ではそのシェーダを実行することができないからです。def sopNodeFlag(self): return self.SOP_DISPLAY_FLAG
-
もし オブジェクトノードが他のノードとリンクされていて、USDでその変換された Prim 間のリンクを再現する必要がある場合、トランスレータクラスに
referencedNodePaths()
を実装します。オブジェクトノードタイプが他のノードを参照していない場合、このメソッドの実装は不要です。一部のHoudiniノードには、取り込むべき重要な他のノードとのリンクがあります。 例えば、環境ライトは、ポータルを表現したジオメトリオブジェクトとリンクすることができ、それを有効にする
portal_anable
パラメータ、ポータルを表現したジオメトリオブジェクトのノードパスを格納するportal_path
パラメータが用意されています。このメソッドでは辞書を返してください。キーは任意(これらのキーによって、情報を
populatePrim()
メソッドに返すことができます)、値はノードパスです。def referencedNodePaths(self): if self._node.evalParm("portal_enable"): return {"portal": self._node.evalParm("portal_path")} else: return {}
-
トランスレータクラスに
populatePrim()
を実装します。ここは、トランスレータの実際の作業が入る場所です。 Scene Import LOPは、以下の引数でこのメソッドをコールします:
prim
オリジナルオブジェクトノードの変換バージョンとして作成されたUSD Prim。 このメソッドの目的は、Houdiniシーンでそのオリジナルオブジェクトノードが行なっていたことと同じ“ジョブ”をUSDステージで行なうために、このPrimにプロパティ/子Primを設定することです。
referenced_node_prim_paths
これは、
referencedNodePaths()
メソッドから返された辞書の値のオブジェクトノードパスを、それに呼応するUSD PrimのSdfパスに置換した辞書です。例えば、
referenced NodePaths()
メソッドが"portal"
とポータルジオメトリを表現したGeometryノードのパスをマッピングした辞書を返した場合、 この引数には、"portal"
とそのGeometryノードから変換されたUSDジオメトリPrimのシーングラフパスをマッピングした辞書を与えます。USD APIを使用して呼応する他のPrim(s)のリンクを再現するかどうかはあなた次第です。例:
def populatePrim(self, prim, referenced_node_prim_paths, force_active): # 受け取った"Raw" PrimをUsdLux.DomeLight APIでラップします。 lgt = UsdLux.DomeLight(prim) # referencedNodePaths()から"portal"リンクが渡されたかどうかをチェックします。 if "portal" in referenced_node_prim_paths: portalPath = referenced_node_prim_paths["portal"] # DomeLight APIを使用して、USDにportalリレーションシップを作成します。 lgt.CreatePortalsRel().SetTargets([portalPath])
force_active
Scene Import LOPには Force Objects パラメータがあって、そのパラメータでは、トランスレータが通常では変換しないものであっても(例えば、トランスレータは非表示ノードまたは“Enabled”タイプのパラメータがオフになっているノードを変換しません)、必ず“アクティブ”としてUSDに常に変換したいオブジェクトノードをユーザがリストすることができます。リストに入れなかったノードは、その場合では不可視または無効なUSD Primとして変換されます。
Force Objects パターンにマッチしたノードに対して、Scene Import LOPは
shouldTranslatePrim()
メソッドをコールせず、 ただ次に進んで、そのPrimを作成して、populatePrim()
メソッドをコールしてそのPrimをセットアップします。 この引数がTrue
に設定されていれば、ユーザはこのPrimを“アクティブ”として作成したいことがわかります。例:
def populatePrim(self, prim, referenced_node_prim_paths, force_active): # 受け取った"Raw" PrimをUsdLux.DomeLight APIでラップします。 lgt = UsdLux.DomeLight(prim) # referencedNodePaths()から"portal"リンクが渡されたかどうかをチェックします。 if "portal" in referenced_node_prim_paths: portalPath = referenced_node_prim_paths["portal"] # DomeLight APIを使用して、USDにportalリレーションシップを作成します。 lgt.CreatePortalsRel().SetTargets([portalPath]) # DomeLight APIを使用してintensityアトリビュートを作成し、オブジェクトノードの"coneangle"パラメータと"conedelta"パラメータに基づいて、 # intensityアトリビュートの値を設定します。 intensity_attr = lgt.CreateIntensityAttr() if force_active: # オブジェクトノードのintensityパラメータから直接intensityアトリビュートを設定します。 self.populateAttr(intensity_attr, self._node.parm("intensity")) else: # オリジナルのライトオブジェクトが無効だった場合、そのintensityアトリビュート値を0に設定し、 # 有効だった場合、そのintensityパラメータの値を使用します。 self.populateAttr(intensity_attr, [self._node.parm("enabled", self._node.parm("intensity"))] lambda vs: vs[0] * vs[1])
パラメータ値をUSDプロパティに変換するには、populateAttr()メソッドを使用してください。 このメソッドは、時間依存とタイムサンプルに関係する多くの記録を自動的に世話します。
populateAttr()の使い方 ¶
Translator.populatePrim()
メソッドでは、self.populateAttr(usd_attr, parm, callback=None)
ヘルパーメソッドを使用して、パラメータのデータをUSDプロパティにコピーしてください。
単純にパラメータ値を読み込んで直接プロパティ値を設定する方が簡単だと思うかもしれません。例:
# Prim上にintensityアトリビュートを作成します。 intensity_attr = lgt.CreateIntensityAttr() # オブジェクトノードのintensityパラメータの値を読み込みます。 intensity_value = self._node.evalParm("intensity") # intensityアトリビュートには、そのパラメータ値を設定します。 intensity_attr.Set(intensity_value)
しかし、パラメータがアニメーションする場合、 これでは間違った結果を招いてしまいます 。
アニメーションを考慮する場合、そのパラメータがアニメーションするかどうかをチェックし、時間依存とタイムサンプルに関係するUSDメタデータをセットアップする必要があります。
このpopulateAttr()
ヘルパーメソッドは、このような処理を自動で行なってくれます。
-
1つ目の引数は、データのコピー先となるUSDプロパティです。まず最初に必ずUSD API(例えば、
prim.CreateAttribute()
やprim.CreatePrimvar()
)を使用してプロパティを作成してください。 -
2つ目の引数には、以下のどれかを指定してください:
-
hou.Parm
オブジェクト。 -
hou.ParmTuple
オブジェクト。 -
hou.Parm
オブジェクトのリスト。 -
スカラー値(文字列、整数、浮動小数点)。
-
None
(この場合では、このメソッドは静かに何もしません)。
2つ目の引数には値を渡すことができるので、場合によっては、(反復などを回避するために)値を事前計算しておきたいかもしれません。例:
# このようにしないでください。 intensity_value = self._node.evalParm("intensity") enabled_value = self._node.evalParm("enabled") if force_active: self.populateAttr(intensity_attr, intensity_value) else: self.populateAttr(intensity_attr, enabled_value * intensity_value)
しかし、オリジナルのパラメータの代わりに値を渡すことで、パラメータに関する重要な情報(例えば、そのパラメータにアニメーションが付いていたかどうか)を
populateAttr()
メソッドから隠してしまいます。代わりに、値ではなく常にパラメータオブジェクトを渡すようにしてください:# このようにしてください。 if force_active: self.populateAttr(intensity_attr, self._node.parm("intensity")) else: self.populateAttr(intensity_attr, [self._node.parm("enabled"), self._node.parm("intensity")], lambda vs: vs[0] * vs[1])
-
-
オプションの3つ目の引数は、コール可能オブジェクト(関数またはラムダ)で、パラメータ(s)から抽出された値に対してコールされます。返された値は、USDプロパティの設定に使用します。オリジナルのパラメータ値をUSDでは別の値に処理する必要がある場合、これを使用してください。
例:
class MyLightTranslator(Translator): def populatePrim(self, prim, referenced_node_prim_paths, force_active): # 受け取った"Raw" PrimをUsdLux.DomeLight APIでラップします。 lgt = UsdLux.DomeLight(prim) # DomeLight APIを使用してradiusアトリビュートを作成し、オブジェクトノードの"coneangle"パラメータと"conedelta"パラメータに基づいて、 # radiusアトリビュートの値を設定します。 self.populateAttr(lgt.CreateRadiusAttr(), [self._node.parm("coneangle"), self._node.parm("conedelta")], lambda vs: vs[0] / 2.0 + vs[1])