すあまの備忘録

誰得内容の自分のための非営利目的備忘録ブログ(筆者がわかっても内緒にしてください)

Solaris - マルチショットと少人数でのデータ共有草案

Universal Scene Description Advent Calendar 2022の記事で書いてるんですが果たしてUSDなのかと言うのは難しいところ。ほとんどHoudiniの記事みたいな…

Solarisでのマルチショットを扱う例と、極少人数を想定したデータ共有例です。

大規模な場合はAsset ResolverやShotGridの連携を前提にする必要がありますが、そこまででもないけどちょっと分業…というか工程が別れているとかがあるみたいなものを想定しています。

あくまで一例として、どういうことができるのか?などの例としてお使いください。

hip

usdScene_gd.hiplc - Google ドライブ

中身に一切の責任を負いません。

マルチショット例

取り敢えずなにかしらショット番号を変える変数と、Switchがあれば処理を分岐してショットを切り替えることができそうです。

すごく単純にそれを実装します。

ショット情報の管理に若干トラブルが起きそうな仕組みにはなっています。

Context Option Editor

まず、Context Option Editorを開きます。

H19.5からデフォルトでいくつかフレームレンジなどのものが用意されていますが無視します。

Windowの+ボタンを押してString Menuとかを作ります。

できたメニューをクリックし、右上をクリックし、画像のように設定します。

これだけでなにかショットを切り替えるMenuのようなものができました!

あとはこれをSwitchにリンクしたりすれば良いわけです。

ちなみにこのContext Option Editorはjsonが読めますし、Pythonでの追加や編集も可能です。もう一歩進んでそこまで自動化しても良いと思います。

Shotを切り替えるSwitch

こんな感じで書き出した各ショットのカメラを3つSwitchにつなぎます。

Edit Propertiesノードをはさみ、各ショットの情報をいれてSwitchを切り替えやすくします。

SwitchのChoose InputをBy Nameにし、Select InputにPythonをいれます。

node = hou.pwd()
for i, input in enumerate(node.inputs()):
    stage = input.stage()
    prim_paths = node.inputPrims(i)
    if not prim_paths:
        continue
    prim = stage.GetPrimAtPath(prim_paths[0])
    attr = prim.GetAttribute("shotInfo:shotNum")
    node.setEditableInputString(i,"inputname",attr.Get())
    
return hou.contextOption("shot")

Pythonがわかる人なら薄々気づいているかもしれませんが、このPythonは読み込んでいるUSDファイルにある情報を読み取っています。

確実に他者に渡した際に動作させるためにも、書き出し時にどのショットなのかの情報を付与しているという感じです。

これで、Context Option Editorで先程作ったShotを変えるとSwitchが連動して切り替わるようになります。

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

データ共有例(書き出し)

↑の例で作ったカメラをショットごとに書き出します。TOPとか絡めてもいいですが今回は手でUSD ROPを3つ作ってます。

Store Parameter ValuesとHoudiniLayerInfo

まず書き出す前にStore Parameter Valuesというノードを使ってStart / EndをHoudiniLayerInfoに書き込んでいます。

なぜ書き込んでいるかというと、この後の処理で必要な階層以外を削除した際でもstart / endを一時的に参照できるようにするためです。

これはこの方法でなくても良いのですが、一緒に紹介しておくと便利なのでわざと使っています。

このノードは値や文字列をScene Graph Tree上に存在するHoudiniLayerInfoというデフォルトでは非表示のlayer info primitiveに格納します。

HoudiniLayerInfoは、USD ROPの書き出し時には含まれない=書き出したファイルが汚れないので一時データを格納できる。という点で便利です。

あとは必ず"/HoudiniLayerInfo"という名前と階層に存在するため、Pythonでのアクセスが容易です。

見えない場合はScene Graph TreeでここをONにすると見えます。

Scene Graph Detailで書き込まれている内容も確認できます。

Store Parameter ValuesでカメラのEdit Propertiesで作成したStart / EndをPythonで書き込みます。

node = hou.pwd()
input = node.input(0)
stage = input.stage()
prim = stage.GetPrimAtPath("/cameras")
attr = prim.GetAttribute("shotInfo:start")
return attr.Get()

このPythonの書き方は覚えておくとStage上のあらゆる部分で値を引っ張ってこれるので便利です。VEXでいうとusd_attrib(<stage>stage, string primpath, string name)と同様です。

書き出すためにUSDを破壊してキレイにする

今回はカメラやLayoutはそれぞれ別で書き出すつもりなのに階層がぐちゃぐちゃです。

USDの作法として読み込んだものはActiveのオンオフかVisibilityしか変えれないという仕様のはずです。

ですが、Restructure Scene Graphノードを使用することで、USDの階層を破壊して書き出すことができます。

ここでは、指定した階層と/collections以外のすべてのトップ階層を削除しています。

※H19.5の最初の方のバージョンまではバグなのか挙動がおかしいです。

このノードのPrimitiveに書き出したい階層をいれることで指定の階層のみのStageを作成できます。

こんな感じに/cameras以外が削除されました。

あとはこれを書き出せばカメラだけのUSDファイルができます。

この仕組みを拡張して、BGのレイアウトとカメラのレイアウトを作成しているStageからそれぞれ個別に書き出すことができます。

※階層をまたいだデータやリレーション等も破壊されます。ルールを決めて使用しましょう。やべーUSDファイルができます。

※Restructure Scene Graphはトップ階層もしくはその一つした階層くらいまでの使用に抑えないと変な削除のされ方をしたりすることがあります。当然ですね。USDを破壊してはいけないので。

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

データ共有例(読み込み)

先程のカメラを読み込みます。が、少人数でもちょっとはバージョン管理したいときありますね?でも毎回手で読んだり…かといってパイプラインで自動化までは難しい…とかの場合の案です。

この方法はアセット管理には向きません。Templateや汎用マテリアルやレンダー設定、RenderVars、各工程のUSDファイルの読み込み向きです。

本来のUSDとしても近しい機能は存在し、Asset Resolverなどがそれに当たります。が、C++だったりビルドしないとダメだったり、開発がいないとなかなか手を出せません。

Asset Resolverはこれに加えてUSDファイル内のバージョンすらも書き換えて読めたりします。

作例

以下が単純なPythonだけで最低限それっぽいことができるようにした例です。

※Reloadが2つあるのは中に入っているLoad LayerとSublayerのものをめんどくさかったのでSubnetにそのまま出しただけです。

何をやっているかというと、Search Directoryに指定したフォルダ以下のv000フォルダを列挙し、書き出したUSDファイルをロードしています。

Latestを指定するとCook時に最新バージョンを読みます。

これでフォルダ階層だけ守ってUSDを書き出してみんなで同じフォルダを読めば後は勝手に更新される仕組みができました。個人や少人数ならこれをちょっとカスタムすれば十分使えたりすると思います。

例えばカメラやLayoutしたBGのデータなどを他の人に渡す場合も、指定したパスにバージョン管理して書き出すだけです。

読み込む側はLatestにしておけば後は勝手にリロード時に最新版が読み込まれます。

仕組みの解説

VersionというOrdered Menuに、Pythonで以下が仕込まれています。

Search Dirで指定したフォルダの下のフォルダの名前をMenuの名前にし、一番最後に見つかったフォルダをLatestに代入します。

import glob
import re
import os
node = hou.pwd()
nodepath = node.path()
parm = node.parm("dir").eval()

label = glob.glob(os.path.join(parm, "**" + os.sep))

result = ["latest","latest"]
for (i,fld) in enumerate(label):
    fld = os.path.basename(fld.rstrip(os.sep))
    result.extend([fld,fld])
    

result = sorted(result)
result[0] = result[-1]
    
return result

上記のPythonの結果をSet Versionに一旦移します。(わかりやすくするためなので実際は不要)

Search Dirと、Versionの結果を元にLoad FileのPythonで以下のように処理します。

import glob
import re
import os
node = hou.pwd()
parm = node.parm("dir").eval()
parmVer = node.parm("setver").eval()
path = parm + parmVer

label = glob.glob(path + "*/*.usd*")

return label[0]

この結果をLoad LayerとSublayerのファイルパスにいれます。

以上、開発とかいなくてもすごく単純な仕組みでマルチショットとバージョン管理できる例です。