From 75c9fccaf1fb9d234d80b91b8c60f35146bfeca2 Mon Sep 17 00:00:00 2001 From: Luca Scheller Date: Sat, 23 Mar 2024 18:44:00 -0700 Subject: [PATCH] 6-Add exposing all paths as an opt-in feature in the Cached and File Resolver. --- CMakeLists.txt | 3 + .../src/resolvers/CachedResolver/PythonAPI.md | 12 ++- docs/src/resolvers/CachedResolver/overview.md | 7 +- docs/src/resolvers/FileResolver/PythonAPI.md | 18 +++++ docs/src/resolvers/FileResolver/overview.md | 6 ++ src/CachedResolver/CMakeLists.txt | 5 +- src/CachedResolver/resolver.cpp | 5 ++ src/CachedResolver/resolver.h | 11 ++- .../testenv/testCachedResolver.py | 36 +++++++++ src/CachedResolver/wrapResolver.cpp | 2 + src/FileResolver/resolver.cpp | 79 +++++++++++-------- src/FileResolver/resolver.h | 10 +++ src/FileResolver/testenv/testFileResolver.py | 57 +++++++++++++ src/FileResolver/wrapResolver.cpp | 2 + 14 files changed, 216 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d5bafe..493c01d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,8 @@ set(AR_FILERESOLVER_USD_PYTHON_MODULE_FULLNAME ${AR_RESOLVER_USD_PYTHON_MODULE_N set(AR_FILERESOLVER_TARGET_LIB fileResolver) set(AR_FILERESOLVER_TARGET_PYTHON _${AR_FILERESOLVER_TARGET_LIB}) set(AR_FILERESOLVER_INSTALL_PREFIX ${AR_PROJECT_NAME}/${AR_FILERESOLVER_USD_PLUGIN_NAME}) +set(AR_FILERESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS "AR_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS" CACHE STRING "Environment variable that controls if absolute path identifiers should be exposed to mapping.") + # Python Resolver option(AR_PYTHONRESOLVER_BUILD "Build the PythonResolver" OFF) if("$ENV{AR_RESOLVER_NAME}" STREQUAL "pythonResolver") @@ -52,6 +54,7 @@ set(AR_CACHEDRESOLVER_USD_PYTHON_EXPOSE_MODULE_NAME PythonExpose) set(AR_CACHEDRESOLVER_TARGET_LIB cachedResolver) set(AR_CACHEDRESOLVER_TARGET_PYTHON _${AR_CACHEDRESOLVER_TARGET_LIB}) set(AR_CACHEDRESOLVER_INSTALL_PREFIX ${AR_PROJECT_NAME}/${AR_CACHEDRESOLVER_USD_PLUGIN_NAME}) +set(AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS "AR_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS" CACHE STRING "Environment variable that controls if absolute path identifiers should be Python exposed.") set(AR_CACHEDRESOLVER_ENV_EXPOSE_RELATIVE_PATH_IDENTIFIERS "AR_EXPOSE_RELATIVE_PATH_IDENTIFIERS" CACHE STRING "Environment variable that controls if relative path identifiers should be Python exposed.") # Http Resolver diff --git a/docs/src/resolvers/CachedResolver/PythonAPI.md b/docs/src/resolvers/CachedResolver/PythonAPI.md index e3bb76b..4eb40b1 100644 --- a/docs/src/resolvers/CachedResolver/PythonAPI.md +++ b/docs/src/resolvers/CachedResolver/PythonAPI.md @@ -26,12 +26,14 @@ As with our mapping and caching pairs, the result is cached in C++ to enable fas As identifiers are context independent, the cache is stored on the resolver itself. See below on how to modify and inspect the cache. +We also have the option to expose any identifier, regardless of absolute/relative/search path based formatting to our `PythonExpose.py` -> `ResolverContext.ResolveAndCache` method by setting the `AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS` environment variable to `1` or by calling `pxr.Ar.GetUnderlyingResolver().SetExposeAbsolutePathIdentifierState(True)`. As this then runs all paths through the Python exposed section, make sure that paths are batch added/pre-cached as much as possible to keep the resolve efficient. + ```python from pxr import Ar, Usd from usdAssetResolver import CachedResolver -# Enable relative identifier modification cached_resolver = Ar.GetUnderlyingResolver() +# Enable relative identifier modification cached_resolver.SetExposeRelativePathIdentifierState(True) print("Resolver is currently exposing relative path identifiers to Python | {}".format(cached_resolver.GetExposeRelativePathIdentifierState())) # Or set the "AR_CACHEDRESOLVER_ENV_EXPOSE_RELATIVE_PATH_IDENTIFIERS" environment variable to 1. @@ -39,6 +41,14 @@ print("Resolver is currently exposing relative path identifiers to Python | {}". cached_resolver.GetExposeRelativePathIdentifierState() # Get the state of exposing relative path identifiers cached_resolver.SetExposeRelativePathIdentifierState() # Set the state of exposing relative path identifiers +# Enable absolute identifier resolving +cached_resolver.SetExposeAbsolutePathIdentifierState(True) +print("Resolver is currently exposing absolute path identifiers to Python | {}".format(cached_resolver.GetExposeAbsolutePathIdentifierState())) +# Or set the "AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS" environment variable to 1. +# This can't be done via Python, as it has to happen before the resolver is loaded. +cached_resolver.GetExposeAbsolutePathIdentifierState() # Get the state of exposing absolute path identifiers +cached_resolver.SetExposeAbsolutePathIdentifierState() # Set the state of exposing absolute path identifiers + # We can also inspect and edit our relative identifiers via the following methods on the resolver. # You'll usually only want to use these outside of the Resolver.CreateRelativePathIdentifier method only for debugging. # The identifier cache is not context dependent (as identifiers are not). diff --git a/docs/src/resolvers/CachedResolver/overview.md b/docs/src/resolvers/CachedResolver/overview.md index 00990b4..34b819b 100644 --- a/docs/src/resolvers/CachedResolver/overview.md +++ b/docs/src/resolvers/CachedResolver/overview.md @@ -10,9 +10,13 @@ By default (similar to the FileResolver and USD's default resolver), any absolut Optionally you can opt-in into also exposing relative identifiers to Python by setting the `AR_CACHEDRESOLVER_ENV_EXPOSE_RELATIVE_PATH_IDENTIFIERS` environment variable to `1` or by calling `pxr.Ar.GetUnderlyingResolver().SetExposeRelativePathIdentifierState(True)`. This is a more advanced feature and is therefore disabled by default. See our [production example](example.md) section for more information on how to use this and why it can be useful. ``` -All non file path identifiers (anything that doesn't start with "/"/"./"/"../") will forward their request to the `PythonExpose.py` -> `ResolverContext.ResolveAndCache` method. +All non file path identifiers (anything that doesn't start with "/", "./", "../") will forward their request to the `PythonExpose.py` -> `ResolverContext.ResolveAndCache` method. If you want to customize this resolver, just edit the methods in PythonExpose.py to fit your needs. You can either edit the file directly or move it anywhere where your "PYTHONPATH"/"sys.path" paths look for Python modules. +```admonish tip title="Pro Tip" +Optionally you can opt-in into also exposing absolute identifiers (so all (absolute/relative/identifiers that don't start with "/","./","../") identifiers) to Python by setting the `AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS` environment variable to `1` or by calling `pxr.Ar.GetUnderlyingResolver().SetExposeAbsolutePathIdentifierState(True)`. This enforces all identifiers to run through Python. Use this with care, we recommend only using this for debugging or when having a large dataset of pre-cached mapping pairs easily available. +``` + We also recommend checking out our unit tests of the resolver to see how to interact with it. You can find them in the "/src/CachedResolver/testenv" folder or on [GitHub](https://github.com/LucaScheller/VFX-UsdAssetResolver/blob/main/src/CachedResolver/testenv/testCachedResolver.py). Here is a full list of features: @@ -22,6 +26,7 @@ Here is a full list of features: - On context creation via the `PythonExpose.py` -> `ResolverContext.Initialize` method. This gets called whenever a context gets created (including the fallback default context). For example Houdini creates the default context if you didn't specify a "Resolver Context Asset Path" in your stage on the active node/in the stage network. If you do specify one, then a new context gets spawned that does the above mentioned mapping pair lookup and then runs the `PythonExpose.py` -> `ResolverContext.Initialize` method. - On resolve for non file path identifiers (anything that doesn't start with "/"/"./"/"../") via the `PythonExpose.py` -> `ResolverContext.ResolveAndCache` method. Here you are free to only add the active asset path via `ctx.AddCachingPair(asset_path, resolved_asset_path)` or any number of relevant asset paths. - We optionally also support hooking into relative path identifier creation via Python. This can be enabled by setting the `AR_CACHEDRESOLVER_ENV_EXPOSE_RELATIVE_PATH_IDENTIFIERS` environment variable to `1` or by calling `pxr.Ar.GetUnderlyingResolver().SetExposeRelativePathIdentifierState(True)`. We then have access in our `PythonExpose.py` -> `Resolver.CreateRelativePathIdentifier` method. Here we can then return a non file path (anything that doesn't start with "/"/"./"/"../") identifier for our relative path, which then also gets passed to our `PythonExpose.py` -> `ResolverContext.ResolveAndCache` method. This allows us to also redirect relative paths to our liking for example when implementing special pinning/mapping behaviours. For more info check out our [production example](./example.md) section. As with our mapping and caching pairs, the result is cached in C++ to enable faster lookups on consecutive calls. As identifiers are context independent, the cache is stored on the resolver itself. See our [Python API](./PythonAPI.md) section on how to clear the cache. +- We optionally also support exposing alle path identifiers to our `ResolverContext.ResolveAndCache` Python method. This can be enabled by setting the `AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS` environment variable to `1` or by calling `pxr.Ar.GetUnderlyingResolver().SetExposeAbsolutePathIdentifierState(True)`. This then forwards any path to be run through our Python exposed method, regardless of how the identifier is formatted. Use this with care, we recommend only using this for debugging or when having a large dataset of pre-cached mapping pairs easily available. - In comparison to our [FileResolver](../FileResolver/overview.md) and [PythonResolver](../PythonResolver/overview.md), the mapping/caching pair values need to point to the absolute disk path (instead of using a search path). We chose to make this behavior different, because in the "PythonExpose.py" you can directly customize the "final" on-disk path to your liking. - The resolver contexts are cached globally, so that DCCs, that try to spawn a new context based on the same mapping file using the [```Resolver.CreateDefaultContextForAsset```](https://openusd.org/dev/api/class_ar_resolver.html), will re-use the same cached resolver context. The resolver context cache key is currently the mapping file path. This may be subject to change, as a hash might be a good alternative, as it could also cover non file based edits via the exposed Python resolver API. - ```Resolver.CreateContextFromString```/```Resolver.CreateContextFromStrings``` is not implemented due to many DCCs not making use of it yet. As we expose the ability to edit the context at runtime, this is also often not necessary. If needed please create a request by submitting an issue here: [Create New Issue](https://github.com/LucaScheller/VFX-UsdAssetResolver/issues/new) diff --git a/docs/src/resolvers/FileResolver/PythonAPI.md b/docs/src/resolvers/FileResolver/PythonAPI.md index 7868544..676dbb6 100644 --- a/docs/src/resolvers/FileResolver/PythonAPI.md +++ b/docs/src/resolvers/FileResolver/PythonAPI.md @@ -11,6 +11,24 @@ Tokens can be found in FileResolver.Tokens: ```python FileResolver.Tokens.mappingPairs ``` +## Resolver +We also have the opt-in feature to expose any identifier, regardless of absolute/relative/search path based formatting to be run through our mapped pairs mapping by setting the `AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS` environment variable to `1` or by calling `pxr.Ar.GetUnderlyingResolver().SetExposeAbsolutePathIdentifierState(True)`. This then forwards any path to be run through our mapped pairs mapping, regardless of how the identifier is formatted. + +```python +from pxr import Ar, Usd +from usdAssetResolver import FileResolver + +file_resolver = Ar.GetUnderlyingResolver() + +# Enable absolute identifier resolving +file_resolver.SetExposeAbsolutePathIdentifierState(True) +print("Resolver is currently exposing absolute path identifiers to the mapping pairs | {}".format(file_resolver.GetExposeAbsolutePathIdentifierState())) +# Or set the "AR_FILERESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS" environment variable to 1. +# This can't be done via Python, as it has to happen before the resolver is loaded. +file_resolver.GetExposeAbsolutePathIdentifierState() # Get the state of exposing absolute path identifiers +file_resolver.SetExposeAbsolutePathIdentifierState() # Set the state of exposing absolute path identifiers +``` + ## Resolver Context You can manipulate the resolver context (the object that holds the configuration the resolver uses to resolve paths) via Python in the following ways: diff --git a/docs/src/resolvers/FileResolver/overview.md b/docs/src/resolvers/FileResolver/overview.md index d75baa1..9d4adec 100644 --- a/docs/src/resolvers/FileResolver/overview.md +++ b/docs/src/resolvers/FileResolver/overview.md @@ -3,9 +3,15 @@ This resolver is a file system based resolver similar to the default resolver with support for custom mapping pairs. {{#include ../shared_features.md:resolverSharedFeatures}} - You can adjust the resolver context content during runtime via exposed Python methods (More info [here](./PythonAPI.md)). Refreshing the stage is also supported, although it might be required to trigger additional reloads in certain DCCs. +- We optionally also support exposing alle path identifiers to our `ResolverContext.ResolveAndCache` Python method. This can be enabled by setting the `AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS` environment variable to `1` or by calling `pxr.Ar.GetUnderlyingResolver().SetExposeAbsolutePathIdentifierState(True)`. This then forwards any path to be run through our mapped pairs mapping, regardless of how the identifier is formatted. + +```admonish tip title="Pro Tip" +Optionally you can opt-in into also exposing absolute identifiers (so all (absolute/relative/identifiers that don't start with "/","./","../") identifiers) to our mapping pair mechanism by setting the `AR_FILERESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS` environment variable to `1` or by calling `pxr.Ar.GetUnderlyingResolver().SetExposeAbsolutePathIdentifierState(True)`. This enforces all identifiers to run through our mapped pairs mapping. The mapped result can also be a search path based path, which then uses the search paths to resolve itself. (So mapping from an absolute to search path based path via the mapping pairs is possible.) +``` {{#include ../shared_features.md:resolverEnvConfiguration}} + ## Debug Codes Adding following tokens to the `TF_DEBUG` env variable will log resolver information about resolution/the context respectively. * `FILERESOLVER_RESOLVER` diff --git a/src/CachedResolver/CMakeLists.txt b/src/CachedResolver/CMakeLists.txt index 6e58e2f..ae2fce6 100644 --- a/src/CachedResolver/CMakeLists.txt +++ b/src/CachedResolver/CMakeLists.txt @@ -56,7 +56,8 @@ target_compile_definitions(${AR_CACHEDRESOLVER_TARGET_LIB} AR_CACHEDRESOLVER_USD_PLUGIN_NAME=${AR_CACHEDRESOLVER_USD_PLUGIN_NAME} AR_CACHEDRESOLVER_USD_PYTHON_MODULE_FULLNAME=${AR_CACHEDRESOLVER_USD_PYTHON_MODULE_FULLNAME} AR_CACHEDRESOLVER_USD_PYTHON_EXPOSE_MODULE_NAME=${AR_CACHEDRESOLVER_USD_PYTHON_EXPOSE_MODULE_NAME} - + AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS=${AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS} + AR_CACHEDRESOLVER_ENV_EXPOSE_RELATIVE_PATH_IDENTIFIERS=${AR_CACHEDRESOLVER_ENV_EXPOSE_RELATIVE_PATH_IDENTIFIERS} ) # Install configure_file(plugInfo.json.in plugInfo.json) @@ -104,6 +105,8 @@ target_compile_definitions(${AR_CACHEDRESOLVER_TARGET_PYTHON} AR_CACHEDRESOLVER_USD_PLUGIN_NAME=${AR_CACHEDRESOLVER_USD_PLUGIN_NAME} AR_CACHEDRESOLVER_USD_PYTHON_MODULE_FULLNAME=${AR_CACHEDRESOLVER_USD_PYTHON_MODULE_FULLNAME} AR_CACHEDRESOLVER_USD_PYTHON_EXPOSE_MODULE_NAME=${AR_CACHEDRESOLVER_USD_PYTHON_EXPOSE_MODULE_NAME} + AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS=${AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS} + AR_CACHEDRESOLVER_ENV_EXPOSE_RELATIVE_PATH_IDENTIFIERS=${AR_CACHEDRESOLVER_ENV_EXPOSE_RELATIVE_PATH_IDENTIFIERS} ) # Install install ( diff --git a/src/CachedResolver/resolver.cpp b/src/CachedResolver/resolver.cpp index 79e44a5..d7aff5e 100644 --- a/src/CachedResolver/resolver.cpp +++ b/src/CachedResolver/resolver.cpp @@ -6,6 +6,7 @@ #include "pxr/base/arch/systemInfo.h" #include "pxr/base/tf/fileUtils.h" +#include "pxr/base/tf/getenv.h" #include "pxr/base/tf/pathUtils.h" #include "pxr/base/tf/pyInvoke.h" #include "pxr/base/tf/staticTokens.h" @@ -86,6 +87,7 @@ _ResolveAnchored( } CachedResolver::CachedResolver() { + this->SetExposeAbsolutePathIdentifierState(TfGetenvBool(DEFINE_STRING(AR_CACHEDRESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS), false)); this->SetExposeRelativePathIdentifierState(TfGetenvBool(DEFINE_STRING(AR_CACHEDRESOLVER_ENV_EXPOSE_RELATIVE_PATH_IDENTIFIERS), false)); }; @@ -327,6 +329,9 @@ CachedResolver::_IsContextDependentPath( const std::string& assetPath) const { TF_DEBUG(CACHEDRESOLVER_RESOLVER_CONTEXT).Msg("Resolver::_IsContextDependentPath()\n"); + if (this->exposeAbsolutePathIdentifierState){ + return true; + } return _IsNotFilePath(assetPath); } diff --git a/src/CachedResolver/resolver.h b/src/CachedResolver/resolver.h index abd8411..bd076e9 100644 --- a/src/CachedResolver/resolver.h +++ b/src/CachedResolver/resolver.h @@ -6,7 +6,6 @@ #include "resolverContext.h" #include "pxr/pxr.h" -#include "pxr/base/tf/getenv.h" #include "pxr/usd/ar/resolver.h" #include @@ -31,6 +30,14 @@ class CachedResolver final : public ArResolver AR_CACHEDRESOLVER_API virtual ~CachedResolver(); + AR_CACHEDRESOLVER_API + bool GetExposeAbsolutePathIdentifierState(){ return this->exposeAbsolutePathIdentifierState; } + AR_CACHEDRESOLVER_API + void SetExposeAbsolutePathIdentifierState(const bool state){ + if (state != this->exposeAbsolutePathIdentifierState){ + this->exposeAbsolutePathIdentifierState = state; + } + } AR_CACHEDRESOLVER_API bool GetExposeRelativePathIdentifierState(){ return this->exposeRelativePathIdentifierState; } AR_CACHEDRESOLVER_API @@ -40,7 +47,6 @@ class CachedResolver final : public ArResolver this->ClearCachedRelativePathIdentifierPairs(); } } - AR_CACHEDRESOLVER_API void AddCachedRelativePathIdentifierPair(const std::string& sourceStr, const std::string& targetStr); AR_CACHEDRESOLVER_API @@ -93,6 +99,7 @@ class CachedResolver final : public ArResolver const CachedResolverContext* _GetCurrentContextPtr() const; CachedResolverContext _fallbackContext; const std::string emptyString{""}; + bool exposeAbsolutePathIdentifierState{false}; bool exposeRelativePathIdentifierState{false}; std::map cachedRelativePathIdentifierPairs; }; diff --git a/src/CachedResolver/testenv/testCachedResolver.py b/src/CachedResolver/testenv/testCachedResolver.py index fdec956..0dff39f 100644 --- a/src/CachedResolver/testenv/testCachedResolver.py +++ b/src/CachedResolver/testenv/testCachedResolver.py @@ -386,6 +386,42 @@ def test_Resolve(self): resolved_path = resolver.Resolve(layer_file_path) self.assertEqual(resolved_path.GetPathString(), layer_file_path) + def test_ResolveAbsoluteIdentifier(self): + with tempfile.TemporaryDirectory() as temp_dir_path: + # Get resolver + resolver = Ar.GetResolver() + cached_resolver = Ar.GetUnderlyingResolver() + # Create files + layer_a_identifier = "layer_a.usd" + layer_a_file_path = os.path.join(temp_dir_path, layer_a_identifier) + Sdf.Layer.CreateAnonymous().Export(layer_a_file_path) + layer_b_identifier = "layer_b.usd" + layer_b_file_path = os.path.join(temp_dir_path, layer_b_identifier) + Sdf.Layer.CreateAnonymous().Export(layer_b_file_path) + # Create context + ctx = CachedResolver.ResolverContext() + ctx.AddCachingPair(layer_a_identifier, layer_a_file_path) + ctx.AddCachingPair(layer_b_identifier, layer_b_file_path) + ctx.AddCachingPair(layer_b_file_path, layer_a_file_path) + # Reset UnitTestHelper + PythonExpose.UnitTestHelper.reset(current_directory_path=temp_dir_path) + with Ar.ResolverContextBinder(ctx): + resolved_path = resolver.Resolve(layer_a_identifier) + self.assertEqual(resolved_path.GetPathString(), layer_a_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + resolved_path = resolver.Resolve(layer_b_identifier) + self.assertEqual(resolved_path.GetPathString(), layer_b_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + # Check that exposed absolute file path resolving works + resolved_path = resolver.Resolve(layer_b_file_path) + self.assertEqual(resolved_path.GetPathString(), layer_b_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + cached_resolver.SetExposeAbsolutePathIdentifierState(True) + resolved_path = resolver.Resolve(layer_b_file_path) + self.assertEqual(resolved_path.GetPathString(), layer_a_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + cached_resolver.SetExposeAbsolutePathIdentifierState(False) + def test_ResolveForNewAsset(self): resolver = Ar.GetResolver() diff --git a/src/CachedResolver/wrapResolver.cpp b/src/CachedResolver/wrapResolver.cpp index 8dbedad..3265586 100644 --- a/src/CachedResolver/wrapResolver.cpp +++ b/src/CachedResolver/wrapResolver.cpp @@ -18,6 +18,8 @@ wrapResolver() class_, AR_BOOST_NAMESPACE::noncopyable> ("Resolver", no_init) + .def("GetExposeAbsolutePathIdentifierState", &This::GetExposeAbsolutePathIdentifierState, return_value_policy(), "Get the state of exposing absolute path identifiers") + .def("SetExposeAbsolutePathIdentifierState", &This::SetExposeAbsolutePathIdentifierState, "Set the state of exposing absolute path identifiers") .def("GetExposeRelativePathIdentifierState", &This::GetExposeRelativePathIdentifierState, return_value_policy(), "Get the state of exposing relative path identifiers") .def("SetExposeRelativePathIdentifierState", &This::SetExposeRelativePathIdentifierState, "Set the state of exposing relative path identifiers") .def("GetCachedRelativePathIdentifierPairs", &This::GetCachedRelativePathIdentifierPairs, return_value_policy(), "Returns all cached relative path identifier pairs as a dict") diff --git a/src/FileResolver/resolver.cpp b/src/FileResolver/resolver.cpp index 5a4a094..b80d989 100644 --- a/src/FileResolver/resolver.cpp +++ b/src/FileResolver/resolver.cpp @@ -1,8 +1,12 @@ +#define CONVERT_STRING(string) #string +#define DEFINE_STRING(string) CONVERT_STRING(string) + #include "resolver.h" #include "resolverContext.h" #include "pxr/base/arch/systemInfo.h" #include "pxr/base/tf/fileUtils.h" +#include "pxr/base/tf/getenv.h" #include "pxr/base/tf/pathUtils.h" #include "pxr/base/tf/pyInvoke.h" #include "pxr/base/tf/staticTokens.h" @@ -69,8 +73,11 @@ _ResolveAnchored( return TfPathExists(resolvedPath) ? ArResolvedPath(TfAbsPath(resolvedPath)) : ArResolvedPath(); } -FileResolver::FileResolver() = default; +FileResolver::FileResolver() { + this->SetExposeAbsolutePathIdentifierState(TfGetenvBool(DEFINE_STRING(AR_FILERESOLVER_ENV_EXPOSE_ABSOLUTE_PATH_IDENTIFIERS), false)); +}; + FileResolver::~FileResolver() = default; std::string @@ -127,43 +134,48 @@ FileResolver::_Resolve( if (assetPath.empty()) { return ArResolvedPath(); } - if (_IsRelativePath(assetPath)) { - if (this->_IsContextDependentPath(assetPath)) { - const FileResolverContext* contexts[2] = {this->_GetCurrentContextPtr(), &_fallbackContext}; - for (const FileResolverContext* ctx : contexts) { - if (ctx) { - auto &mappingPairs = ctx->GetMappingPairs(); - std::string mappedPath = assetPath; - if (!mappingPairs.empty()){ - if (!ctx->GetMappingRegexExpressionStr().empty()) - { - mappedPath = std::regex_replace(mappedPath, - ctx->GetMappingRegexExpression(), - ctx->GetMappingRegexFormat()); - TF_DEBUG(FILERESOLVER_RESOLVER_CONTEXT).Msg("Resolver::_CreateDefaultContextForAsset('%s')" - " - Mapped to '%s' via regex expression '%s' with formatting '%s'\n", - assetPath.c_str(), - mappedPath.c_str(), - ctx->GetMappingRegexExpressionStr().c_str(), - ctx->GetMappingRegexFormat().c_str()); - } + + if (this->_IsContextDependentPath(assetPath)) { + const FileResolverContext* contexts[2] = {this->_GetCurrentContextPtr(), &_fallbackContext}; + for (const FileResolverContext* ctx : contexts) { + if (ctx) { + auto &mappingPairs = ctx->GetMappingPairs(); + std::string mappedPath = assetPath; + if (!mappingPairs.empty()){ + if (!ctx->GetMappingRegexExpressionStr().empty()) + { + mappedPath = std::regex_replace(mappedPath, + ctx->GetMappingRegexExpression(), + ctx->GetMappingRegexFormat()); + TF_DEBUG(FILERESOLVER_RESOLVER_CONTEXT).Msg("Resolver::_CreateDefaultContextForAsset('%s')" + " - Mapped to '%s' via regex expression '%s' with formatting '%s'\n", + assetPath.c_str(), + mappedPath.c_str(), + ctx->GetMappingRegexExpressionStr().c_str(), + ctx->GetMappingRegexFormat().c_str()); } - auto map_find = mappingPairs.find(mappedPath); - if(map_find != mappingPairs.end()){ - mappedPath = map_find->second; + } + auto map_find = mappingPairs.find(mappedPath); + if(map_find != mappingPairs.end()){ + mappedPath = map_find->second; + } + + if (this->exposeAbsolutePathIdentifierState) { + if (!_IsSearchPath(mappedPath)){ + return _ResolveAnchored(std::string(), mappedPath);; } - for (const auto& searchPath : ctx->GetSearchPaths()) { - ArResolvedPath resolvedPath = _ResolveAnchored(searchPath, mappedPath); - if (resolvedPath) { - return resolvedPath; - } + } + + for (const auto& searchPath : ctx->GetSearchPaths()) { + ArResolvedPath resolvedPath = _ResolveAnchored(searchPath, mappedPath); + if (resolvedPath) { + return resolvedPath; } - // Only try the first valid context. - break; } + // Only try the first valid context. + break; } } - return ArResolvedPath(); } return _ResolveAnchored(std::string(), assetPath); } @@ -234,6 +246,9 @@ FileResolver::_IsContextDependentPath( const std::string& assetPath) const { TF_DEBUG(FILERESOLVER_RESOLVER_CONTEXT).Msg("Resolver::_IsContextDependentPath()\n"); + if (this->exposeAbsolutePathIdentifierState){ + return true; + } return _IsSearchPath(assetPath); } diff --git a/src/FileResolver/resolver.h b/src/FileResolver/resolver.h index 9ca7d82..5cd0072 100644 --- a/src/FileResolver/resolver.h +++ b/src/FileResolver/resolver.h @@ -31,6 +31,15 @@ class FileResolver final : public ArResolver AR_FILERESOLVER_API virtual ~FileResolver(); + AR_FILERESOLVER_API + bool GetExposeAbsolutePathIdentifierState(){ return this->exposeAbsolutePathIdentifierState; } + AR_FILERESOLVER_API + void SetExposeAbsolutePathIdentifierState(const bool state){ + if (state != this->exposeAbsolutePathIdentifierState){ + this->exposeAbsolutePathIdentifierState = state; + } + } + protected: AR_FILERESOLVER_API std::string _CreateIdentifier( @@ -82,6 +91,7 @@ class FileResolver final : public ArResolver private: const FileResolverContext* _GetCurrentContextPtr() const; FileResolverContext _fallbackContext; + bool exposeAbsolutePathIdentifierState{false}; }; PXR_NAMESPACE_CLOSE_SCOPE diff --git a/src/FileResolver/testenv/testFileResolver.py b/src/FileResolver/testenv/testFileResolver.py index 4079e7a..33a2268 100644 --- a/src/FileResolver/testenv/testFileResolver.py +++ b/src/FileResolver/testenv/testFileResolver.py @@ -170,6 +170,63 @@ def test_Resolve(self): resolved_path = resolver.Resolve(layer_file_path) self.assertEqual(resolved_path.GetPathString(), layer_file_path) + def test_ResolveAbsoluteIdentifier(self): + with tempfile.TemporaryDirectory() as temp_dir_path: + # Get resolver + resolver = Ar.GetResolver() + cached_resolver = Ar.GetUnderlyingResolver() + # Create files + layer_a_identifier = "layer_a.usd" + layer_a_file_path = os.path.join(temp_dir_path, layer_a_identifier) + Sdf.Layer.CreateAnonymous().Export(layer_a_file_path) + layer_b_identifier = "layer_b.usd" + layer_b_file_path = os.path.join(temp_dir_path, layer_b_identifier) + Sdf.Layer.CreateAnonymous().Export(layer_b_file_path) + layer_c_identifier = "layer_c.usd" + layer_c_file_path = os.path.join(temp_dir_path, layer_c_identifier) + Sdf.Layer.CreateAnonymous().Export(layer_c_file_path) + # Create context + ctx = FileResolver.ResolverContext() + ctx.SetCustomSearchPaths([temp_dir_path]) + ctx.RefreshSearchPaths() + ctx.SetMappingRegexExpression("(v\d\d\d)") + ctx.SetMappingRegexFormat("v000") + ctx.AddMappingPair(layer_a_identifier, layer_a_identifier) + ctx.AddMappingPair(layer_b_identifier, layer_b_identifier) + ctx.AddMappingPair(layer_b_file_path, layer_a_file_path) + with Ar.ResolverContextBinder(ctx): + resolved_path = resolver.Resolve(layer_a_identifier) + self.assertEqual(resolved_path.GetPathString(), layer_a_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + resolved_path = resolver.Resolve(layer_b_identifier) + self.assertEqual(resolved_path.GetPathString(), layer_b_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + resolved_path = resolver.Resolve(layer_c_file_path) + self.assertEqual(resolved_path.GetPathString(), layer_c_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + # Check that exposed absolute file path resolving works + resolved_path = resolver.Resolve(layer_b_file_path) + self.assertEqual(resolved_path.GetPathString(), layer_b_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + cached_resolver.SetExposeAbsolutePathIdentifierState(True) + resolved_path = resolver.Resolve(layer_b_file_path) + self.assertEqual(resolved_path.GetPathString(), layer_a_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + # Support special case of mapping absolute paths to search paths + ctx.AddMappingPair(layer_b_file_path, layer_c_identifier) + resolved_path = resolver.Resolve(layer_b_file_path) + self.assertEqual(resolved_path.GetPathString(), layer_c_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + cached_resolver.SetExposeAbsolutePathIdentifierState(False) + # Make sure if the feature is off, absolute paths still get resolved as usual. + ctx.ClearMappingPairs() + resolved_path = resolver.Resolve(layer_a_file_path) + self.assertEqual(resolved_path.GetPathString(), layer_a_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + resolved_path = resolver.Resolve(layer_b_file_path) + self.assertEqual(resolved_path.GetPathString(), layer_b_file_path) + self.assertTrue(os.path.isabs(resolved_path.GetPathString())) + def test_ResolveForNewAsset(self): resolver = Ar.GetResolver() diff --git a/src/FileResolver/wrapResolver.cpp b/src/FileResolver/wrapResolver.cpp index d36fbe7..8eec0d4 100644 --- a/src/FileResolver/wrapResolver.cpp +++ b/src/FileResolver/wrapResolver.cpp @@ -18,5 +18,7 @@ wrapResolver() class_, AR_BOOST_NAMESPACE::noncopyable> ("Resolver", no_init) + .def("GetExposeAbsolutePathIdentifierState", &This::GetExposeAbsolutePathIdentifierState, return_value_policy(), "Get the state of exposing absolute path identifiers") + .def("SetExposeAbsolutePathIdentifierState", &This::SetExposeAbsolutePathIdentifierState, "Set the state of exposing absolute path identifiers") ; } \ No newline at end of file