Relative Asset Paths and the Default Resolver

I was attempting to get the resolvedPath for an SdfAssetPath attribute/input with the default resolver by doing this:

from pxr import Ar
relativeAssetPath = "./textures/Fieldstone/Fieldstone_BaseColor.png"
resolvedRelativePath = Ar.GetResolver().Resolve(relativeAssetPath)
print(f"resolvedRelativePath: {resolvedRelativePath}")

resolvedRelativePath will be empty. I then assumed that since there was no “context” that it I must provide search paths, as the default resolver documentation suggests:

from pxr import Ar
relativeAssetPath = "./textures/Fieldstone/Fieldstone_BaseColor.png"
with Ar.ResolverContextBinder(usdviewApi.stage.GetPathResolverContext()):
    resolvedRelativePath = Ar.GetResolver().Resolve(relativeAssetPath)
    print(f"resolvedRelativePath: {resolvedRelativePath}")

This also leaves resolvedRelativePath empty.

I then debugged the code to find that ArDefaultResolver::_Resolve() will only use the search paths provided in the context if the input assetPath is a “search path”. It looks to me like a “search path” is a relative path without ./ or ../.

According to the Default Resolver Details, it will resolve relative paths by using the search paths as anchors, but in practice it only uses CWD() as an anchor.

In order to resolve assets specified by relative paths, this resolver implements a simple “search path” scheme. The resolver will anchor the relative path to a series of directories and return the first absolute path where the asset exists.

Maybe this Default Resolver Details section could be rephrased to indicate the difference between a “search” relative path and an “anchored” relative path in the context of the default resolver so others aren’t tripped up by this behavior:

  • search relative path: directory/file.ext, file.ext
    • search paths are also relative paths and resolved by the default resolver using the resolver’s context paths
  • anchored relative path: ./directory/file.ext, ../file.ext, ./file.ext
    • these paths are “bypassed”… do we call these “anchored” relative paths?

I did a lot of digging and find a few instances where it is noted that the default resolver doesn’t resolve ./ relative paths:

  • “asset identifiers prefixed with ./ are incorrect” in this issue
  • “anchored paths, those that start with “./” like @./anchored/layer.usd@ won’t work properly” in this post

My final solution was uncovered when I found this post with this enlightening text:

if the authored path is an “anchored path” like @./udimsfolder/udim_texture_.jpg@ , the returned SdfAssetPath that the UsdAttribute gives you will have already performed the anchoring to the layer’s location for you, so you will get back a different and more complete string than the one that was authored

I found that the following code does the asset resolving that we require, but I wonder why “anchored” relative paths are treated differently by the default resolver:

from pxr import Sdf, Usd, UsdShade
matPrim = UsdShade.Material(usdviewApi.stage.GetPrimAtPath("/World/Looks/cubePbr"))
textureAssetPath = matPrim.GetInput("DiffuseTexture").Get()
print(f"assetPath: {textureAssetPath.path}")
print(f"resolvedAssetPath: {textureAssetPath.resolvedPath}")

Finally, resolvedAssetPath contains a resolved path that I can use :slight_smile:

All of this is an attempt to avoid any examination on the “logical” asset path string and use the “resolved” asset path.

I have uploaded an example stage zip that can be opened in USDView and all of this code can be run within the Python Interpreter window.

1 Like

I do agree the docs could perhaps be a little more up front about the behaviour. It might be worth filing an issue (or even an PR) against the OpenUSD repo to ask for adding the clarifications you found here.