Skip to content

AssetResolution(2) ResolveとContext

Universal Scene Description AdventCalendar2021 17 日目は、
USD の Asset Resolution (アセットパスの解決) 第二回目。

前回の AssetResolution(1) - usdResolverExample に続いて、USD のツールセットの1つ usdresolve についてと Python を使用したパス解決方法です。

Note

USDTool 下に記事を書くか迷いましたが、
割合的に usdresolve 要素はわずかなので、前回の続きとして記事は書きます

usdresolve

まず、USD のツールセットには usdresolve というコマンドラインツールが存在します。

https://graphics.pixar.com/usd/release/toolset.html#usdresolve

これは、前回説明した AssetResolver をコマンドラインから実行するためのツールです。
このコマンドラインを使用すると、AssetResolver で行っている AssetResolution を実行して
USD の AssetPath から最終的なリソースへのパスを得ることができます。

まず、DefaultResolver を使用して、相対パスを絶対パスに変換したい場合。

1
usdresolve ./assets/CeilingLight/CeilingLight.usd --anchorPath D:\Kitchen_set\Kitchen_set.usd

D:/Kitchen_set/assets/CeilingLight/CeilingLight.usd

Kitchen_set.usd 内のある AssetPath の解決済のパスを取得したい場合は anchorPath に、
解決対象の Path の基準になる(ロードしている)USD の AssetPath を指定します。
指定すると、指定のパス( ./assets/CeilingLight/CeilingLight.usd ) の anchorPath 基準の絶対パスにして取得することができます。

次に、前回作成したようなカスタムの AssetResolver を使用する場合。

1
usdresolve asset:/Buzz/{$VERSION}/Buzz.usd --createContextFromString D:\testUsdResolverExample\shots\shot_1\versions.json

asset:/Buzz/latest/Buzz.usd

カスタムの Resolver の場合、解決用の Context は都度変わりますが usdREsolverExample の場合、 version.json を
createContextFromString に渡すことで、パスの解決をすることができます。

このように、usdresolve ツールは、引数に 未解決の AssetPath と
解決に必要な情報 (anchorPath であったり context であったり)を与えることで、解決済の AssetPath をプリントしてくれます。

DEBUG メッセージをみてみる

1
set TF_DEBUG=USD_RESOLVER_EXAMPLE

実際のところ、どう AssetResolution が行われていて
結果どのようなパスになっているのかがわかりにくいので Debug メッセージをいれてみます。

たとえば、asset URI ではないファイルを実行してみたばあいも、UsdResolverExampleResolver が呼ばれているものの
Context が見つからないため、DefaultResolver が呼ばれていることがわかりますし、

Asset が見つかった場合は、このように FileSystem 上に見つかっていることがわかります。

Python で実行する

コマンドラインを使用するのなら以上で終了なのですが、
今回は Python から使用したい場合も併せて試してみます。

anchorPath の場合

1
2
3
4
5
resolver = Ar.GetResolver()

anchorPath = Ar.ResolvedPath("D:/Kitchen_set/Kitchen_set.usd")
assetPath = "./assets/Ball/Ball.usd"
fullPath = resolver.CreateIdentifier(assetPath,anchorPath)

anchorPath の場合は、ある解決済の Path をもとに、assetPath を解決することができる CreateIdentifierForNewAsset 関数を使用します。
ArResolvedPath は、その名の通り解決済の AssetPath です。
CreateIdentifier は、この解決済の ArResolvedPath からのパスを取得することができます。

以前 AssetInfo で解説したでも
この ResolvedPath と Resolver の CreateIdentifier を使用して、指定のレイヤーからの相対パスを解決していました。

Resolve を使用する場合

上記の例の場合は、指定ファイルからのパスを取得することができました。
(./~~から指定される、現在のレイヤーからの相対パス)

これとは別に、AssetResolver の Resolve を使用して AssetResolution を実行する事ができます。

ArResolver ArResolverContextBinder ArResolverContext

AssetResolver は、大きく分けると ArResolver ArResolverContextBinder ArResolverContext の3つに分かれています。
ArResolverContext とは、AssetResolution を行うために Resolver に追加情報を与えるための機能 です。
そしてその Context を Resolver で使用できるようにするのに ArResolverContextBinder を使用します。

ArResolverContext とは、
usdResolverExample の場合でいうと、バージョンが書かれている json ファイルの Path です。
usdResolverExample 本体側で、この Context から json を受け取り、AssetResolution を行います。

DefaultResolver の場合、この ArResolverContext に、PXR_DEFAULT_SEARCH_PATH を渡して
パスの検索を行っています。

まずは、DefaultResolver を使用した場合。
DefaultResolver は、PXR_DEFAULT_SEARCH_PATH を、; で分割した文字列を、ArResolver で順番に検索し
見つかった Path を ResolvedPath として返します。

1
2
3
4
5
6
7
from pxr import Ar

resolver = Ar.GetResolver()

context = resolver.CreateContextFromString("D:/sample;D:/Kitchen_set")
with Ar.ResolverContextBinder(context):
    print(resolver.Resolve("assets/Ball/Ball.usd"))

GetResolver で ArResolver オブジェクトを取得します。

Python で実行した場合、
ContextBinder に対して Context を指定するのですが、その有効範囲は with で指定します。
with 内では、 Binder で指定した Context を使用して Resolution が行われるので
今回の場合なら、Resolve 関数は
検索対象の Path(サンプルの場合 D:/sample D:/Kitchen_set ) を順番に検索し見つかったパスを返します。

URI を使用した場合

次はカスタムした Resolver を使用した場合。

1
2
3
4
5
6
7
8
9
resolver = Ar.GetResolver()

ASSETS_DIR = "D:/testUsdResolverExample/assets/"

ctx = resolver.CreateContextFromString('asset',r"D:\testUsdResolverExample\shots\shot_1\versions.json")
# あるいは ctx  = UsdResolverExample.ResolverContext(r"D:\testUsdResolverExample\shots\shot_1\versions.json")
with Ar.ResolverContextBinder(ctx):
    resolved = resolver.Resolve("asset:Buzz/{$VERSION}/Buzz.usd")
    print(resolved.GetPathString().replace("asset:",ASSETS_DIR))

URI を使用している場合は、指定の Context を CreateContextFromString に URI を指定することで取得します。
あるいは、Python のモジュールを作成している場合はそれ経由でカスタムの Context を取得します。

Context は Resolver で使用する追加情報を指定します。
(今回の場合は version を書いた json のパス)

Resover は、Context を経由して version.json を受け取ります。
そして、version.json をロードし、その中にある AssetName のバージョンを取得します。
そしてそのバージョンを使用することで、AssetName 以下の未解決の $VERSION を置換し、最終的な Asset の FullPath を解決します。

usdResolverExample の場合は、Resolve で取得できる Path は URI の Path なので
asset: を USD_RESOLVER_EXAMPLE_ASSET_DIR に置換していますが、(サンプルでは AssetOpen 時に _GetFilesystemPath(resolvedPath) で FullPath 取得)
それ以外の「どのバージョンを使用するか」は Resolve を使用することで最終的にどの Path が使用されているのかわかりました。

Info

Resolver には(Resolver に限らず Layer などでも) ResolveForNewAsset のように ほぼ同じような処理だけど NewAsset とつく
関数が用意されています。
これは、新規アセットの場合ファイルが存在しない可能性があるので
それによって処理を分岐させているようでした。
Resolve の場合は、既にファイルがあることを期待して見つからない場合はからの ArResolvedPath を
返す処理が入っていたり。

まとめ

2回にわたって AssetResolution を見てきました。
Resolver を使用すれば、FilePath の解決をいろいろカスタマイズできそうですし
データベースや、各種クラウドストレージの活用を
USD のパイプラインで実装することができます。

この機能は、コンポジションやスキーマといった強力な機能と同じぐらい魅力的なものであり
USD を使用したパイプラインを支える強力な武器でもあります。

AssetResolver2.0 になり、わかりやすいサンプルも追加されたことで
より導入がしやすくなったと思いますので
USD を導入する際には活用していただければなと思います。

Info

Python での使用方法は

1
2
3
4
> extras/usd/examples/usdResolverExample/testenv/testUsdResolveExample.py

を参考にしています。
C++の実装と合わせてテストコードをみると、より分かりやすいです