}
struct UsdUtils_ExtractExternalReferencesClient {
- void
+ UsdUtilsDependencyInfo
Process (
const SdfLayerRefPtr &,
const std::string &assetPath,
PlaceAsset(dependency, dependencyType);
}
}
+
+ return {};
}
void PlaceAsset(
PXR_NAMESPACE_OPEN_SCOPE
+static
+std::vector<std::string>
+_AllDependenciesForInfo(
+ const UsdUtilsDependencyInfo &depInfo)
+{
+ const std::vector<std::string>& assetDeps = depInfo.GetDependencies();
+ std::vector<std::string> dependencies;
+ dependencies.reserve((assetDeps.size() + 1));
+ dependencies.insert(dependencies.end(), assetDeps.begin(), assetDeps.end());
+ dependencies.emplace_back(depInfo.GetAssetPath());
+
+ return dependencies;
+}
+
// Processes sublayer paths, removing duplicates and only updates the paths in
// the writable layer if the processed list differs from the source list.
std::vector<std::string>
}
}
-std::vector<std::string>
-UsdUtils_WritableLocalizationDelegate::_AllDependenciesForInfo(
- const UsdUtilsDependencyInfo &depInfo)
-{
- const std::vector<std::string>& assetDeps = depInfo.GetDependencies();
- std::vector<std::string> dependencies;
- dependencies.reserve((assetDeps.size() + 1));
- dependencies.insert(dependencies.end(), assetDeps.begin(), assetDeps.end());
- dependencies.emplace_back(depInfo.GetAssetPath());
-
- return dependencies;
-}
-
SdfLayerRefPtr
UsdUtils_WritableLocalizationDelegate::_GetOrCreateWritableLayer(
const SdfLayerRefPtr& layer)
UsdUtils_ReadOnlyLocalizationDelegate::ProcessSublayers(
const SdfLayerRefPtr &layer)
{
+ std::vector<std::string> dependencies;
+
for (const auto &path : layer->GetSubLayerPaths()) {
- _processingFunc(layer, path, {}, UsdUtils_DependencyType::Sublayer);
+ UsdUtilsDependencyInfo info = _processingFunc(
+ layer, path, {}, UsdUtils_DependencyType::Sublayer);
+
+ if (info.GetAssetPath().empty()) {
+ continue;
+ }
+
+ dependencies.emplace_back(info.GetAssetPath());
+ dependencies.insert(dependencies.end(),
+ info.GetDependencies().begin(), info.GetDependencies().end());
}
return {};
const SdfLayerRefPtr &layer,
const SdfPrimSpecHandle &primSpec)
{
- for (auto const& payload: primSpec->GetPayloadList().GetAppliedItems()) {
- // If the asset path is empty this is a local payload. We can ignore
- // these since they refer to the same layer where the payload was
- // authored.
- if (payload.GetAssetPath().empty()) {
- continue;
- }
-
- _processingFunc(layer, payload.GetAssetPath(), {},
- UsdUtils_DependencyType::Payload);
- }
-
- return {};
+ return ProcessReferencesOrPayloads
+ <SdfPayload, UsdUtils_DependencyType::Payload>(
+ layer, primSpec->GetPayloadList().GetAppliedItems());
}
std::vector<std::string>
const SdfLayerRefPtr &layer,
const SdfPrimSpecHandle &primSpec)
{
- SdfReferencesProxy references = primSpec->GetReferenceList();
-
- for (const auto& reference: references.GetAppliedItems()) {
- _ProcessRefOrPayload<SdfReference, UsdUtils_DependencyType::Reference>(
- layer, reference);
- }
-
- return {};
+ return ProcessReferencesOrPayloads
+ <SdfReference, UsdUtils_DependencyType::Reference>(
+ layer, primSpec->GetReferenceList().GetAppliedItems());
}
-template <class RefOrPayloadType, UsdUtils_DependencyType DEP_TYPE>
-void
-UsdUtils_ReadOnlyLocalizationDelegate::_ProcessRefOrPayload(
+
+template <typename RefOrPayloadType, UsdUtils_DependencyType dependencyType>
+std::vector<std::string>
+UsdUtils_ReadOnlyLocalizationDelegate::ProcessReferencesOrPayloads(
const SdfLayerRefPtr &layer,
- const RefOrPayloadType& refOrPayload)
+ const std::vector<RefOrPayloadType>& appliedItems)
{
- // If the asset path is empty this is a local ref/payload. We can ignore
- // these since they refer to the same layer where the payload was
- // authored.
- if (refOrPayload.GetAssetPath().empty()) {
- return;
+ std::vector<std::string> dependencies;
+
+ for (const auto& refOrPayload: appliedItems) {
+ // If the asset path is empty this is a local reference or payload.
+ // We can ignore these since they refer to the same layer where it was
+ // authored.
+ if (refOrPayload.GetAssetPath().empty()) {
+ continue;
+ }
+
+ UsdUtilsDependencyInfo info = _processingFunc(layer,
+ refOrPayload.GetAssetPath(), {}, dependencyType);
+
+ if (info.GetAssetPath().empty()) {
+ continue;
+ }
+
+ dependencies.emplace_back(info.GetAssetPath());
+ dependencies.insert(dependencies.end(),
+ info.GetDependencies().begin(), info.GetDependencies().end());
}
- _processingFunc(layer, refOrPayload.GetAssetPath(), {}, DEP_TYPE);
+ return dependencies;
}
std::vector<std::string>
const std::string &authoredPath,
const std::vector<std::string> &dependencies)
{
- _processingFunc(layer, authoredPath, dependencies,
- UsdUtils_DependencyType::Reference);
+ return _AllDependenciesForInfo(_processingFunc(layer, authoredPath,
+ dependencies, UsdUtils_DependencyType::Reference));
return {};
}
const std::string &authoredPath,
const std::vector<std::string> &dependencies)
{
- _processingFunc(layer, authoredPath, dependencies,
- UsdUtils_DependencyType::Reference);
-
- return {};
+ return _AllDependenciesForInfo(_processingFunc(layer, authoredPath,
+ dependencies, UsdUtils_DependencyType::Reference));
}
std::vector<std::string>
const std::string &templateAssetPath,
std::vector<std::string> dependencies)
{
- _processingFunc(layer, templateAssetPath, dependencies,
- UsdUtils_DependencyType::ClipTemplateAssetPath);
-
- return {};
+ return _AllDependenciesForInfo(_processingFunc(
+ layer, templateAssetPath, dependencies,
+ UsdUtils_DependencyType::ClipTemplateAssetPath));
}
PXR_NAMESPACE_CLOSE_SCOPE
SdfLayerRefPtr _GetOrCreateWritableLayer(const SdfLayerRefPtr& layer);
static std::string _GetRelativeKeyPath(const std::string& fullPath);
-
- static std::vector<std::string> _AllDependenciesForInfo(
- const UsdUtilsDependencyInfo &depInfo);
// the user supplied processing function that will be invoked on every path.
ProcessingFunc _processingFunc;
: public UsdUtils_LocalizationDelegate
{
public:
- using ProcessingFunc = std::function<void(
+ using ProcessingFunc = std::function<UsdUtilsDependencyInfo(
const SdfLayerRefPtr &layer,
const std::string &assetPath,
const std::vector<std::string> &dependencies,
std::vector<std::string> dependencies) override;
private:
- template <class RefOrPayloadType, UsdUtils_DependencyType DEP_TYPE>
- void _ProcessRefOrPayload(
+ template <typename RefOrPayloadType, UsdUtils_DependencyType DepType>
+ std::vector<std::string> ProcessReferencesOrPayloads(
const SdfLayerRefPtr &layer,
- const RefOrPayloadType& refOrPayload);
+ const std::vector<RefOrPayloadType>& appliedItems);
ProcessingFunc _processingFunc;
};
struct UsdUtils_ComputeAllDependenciesClient
{
- void
+ UsdUtils_ComputeAllDependenciesClient(
+ const std::function<UsdUtilsProcessingFunc> &processingFunc)
+ :processingFunc(processingFunc) {}
+
+ UsdUtilsDependencyInfo
Process(
const SdfLayerRefPtr &layer,
const std::string & assetPath,
const std::vector<std::string> &dependencies,
UsdUtils_DependencyType dependencyType)
{
+
+ if (processingFunc) {
+ UsdUtilsDependencyInfo depInfo = {assetPath, dependencies};
+ UsdUtilsDependencyInfo processedInfo =
+ processingFunc(layer, depInfo);
+
+ if (processedInfo.GetAssetPath().empty()) {
+ return {};
+ }
+
+ // When using a processing function with template asset paths
+ // such as clips or udim, if the user does not modify the
+ // asset path, we do not want to place it in the resulting arrays
+ // We always want to add dependencies, however
+ bool originalPathIsTemplate = !dependencies.empty();
+ if (processedInfo != depInfo || !originalPathIsTemplate) {
+ PlaceAsset(layer, processedInfo.GetAssetPath(), dependencyType);
+ }
+
+ for (const auto & dependency : processedInfo.GetDependencies()) {
+ PlaceAsset(layer, dependency, dependencyType);
+ }
+
+ return processedInfo;
+ }
+
if (dependencies.empty()) {
PlaceAsset(layer, assetPath, dependencyType);
}
PlaceAsset(layer, dependency, dependencyType);
}
}
+
+ return {};
}
bool
std::vector<SdfLayerRefPtr> layers;
std::vector<std::string> assets, unresolvedPaths;
+ std::function<UsdUtilsProcessingFunc> processingFunc;
};
bool
const SdfAssetPath &assetPath,
std::vector<SdfLayerRefPtr> *outLayers,
std::vector<std::string> *outAssets,
- std::vector<std::string> *outUnresolvedPaths)
+ std::vector<std::string> *outUnresolvedPaths,
+ const std::function<UsdUtilsProcessingFunc> &processingFunc)
{
SdfLayerRefPtr rootLayer = SdfLayer::FindOrOpen(assetPath.GetAssetPath());
return false;
}
- UsdUtils_ComputeAllDependenciesClient client;
+ UsdUtils_ComputeAllDependenciesClient client(processingFunc);
UsdUtils_ReadOnlyLocalizationDelegate delegate(
std::bind(&UsdUtils_ComputeAllDependenciesClient::Process, &client,
std::placeholders::_1, std::placeholders::_2,
#include "pxr/pxr.h"
#include "pxr/usd/usdUtils/api.h"
#include "pxr/usd/usdUtils/usdzPackage.h"
+#include "pxr/usd/usdUtils/userProcessingFunc.h"
#include <string>
#include <vector>
/// All of the resolved non-layer dependencies are populated in \p assets.
/// Any unresolved (layer and non-layer) asset paths are populated in
/// \p unresolvedPaths.
-///
+///
+/// If a function is provided for the \p processingFunc parameter, it will be
+/// invoked on every asset path that is discovered during localization.
+/// Refer to \ref UsdUtilsDependencyInfo for general information on User
+/// processing functions. Any changes made to the paths during the
+/// invocation of this function will not be written to processed layers.
+///
/// The input vectors to be populated with the results are *cleared* before
/// any results are added to them.
///
/// Returns true if the given asset was resolved correctly.
USDUTILS_API
bool
-UsdUtilsComputeAllDependencies(const SdfAssetPath &assetPath,
- std::vector<SdfLayerRefPtr> *layers,
- std::vector<std::string> *assets,
- std::vector<std::string> *unresolvedPaths);
+UsdUtilsComputeAllDependencies(
+ const SdfAssetPath &assetPath,
+ std::vector<SdfLayerRefPtr> *layers,
+ std::vector<std::string> *assets,
+ std::vector<std::string> *unresolvedPaths,
+ const std::function<UsdUtilsProcessingFunc> &processingFunc =
+ std::function<UsdUtilsProcessingFunc>());
/// Callback that is used to modify asset paths in a layer. The \c assetPath
/// will contain the string value that's authored. The returned value is the
/// new value that should be authored in the layer. If the function returns
/// an empty string, that value will be removed from the layer.
using UsdUtilsModifyAssetPathFn = std::function<std::string(
- const std::string& assetPath)>;
+ const std::string& assetPath)>;
/// Helper function that visits every asset path in \c layer, calls \c modifyFn
/// and replaces the value with the return value of \c modifyFn. This modifies
/// for example.
USDUTILS_API
void UsdUtilsModifyAssetPaths(
- const SdfLayerHandle& layer,
- const UsdUtilsModifyAssetPathFn& modifyFn);
+ const SdfLayerHandle& layer,
+ const UsdUtilsModifyAssetPathFn& modifyFn);
PXR_NAMESPACE_CLOSE_SCOPE
/// If a function is provided for the \p processingFunc parameter, it will be
/// invoked on every asset path that is discovered during localization. This
/// allows you to inject your own logic into the process. Refer to
-/// \ref UsdUtilsDependencyInfo for additional information.
+/// \ref UsdUtilsDependencyInfo for general information on user processing
+/// functions. If an asset path is ignored in the processing function, it will
+/// be removed from the layer and excluded from the localized package. Paths
+/// that are modified will have their updated value written back into the
+/// localized layer. Paths that are added to the dependencies array during
+/// processing will be included in the resulting localized asset.
///
/// Returns true if the package was created successfully.
///
Processing functions are able to perform a number of tasks which have an effect
on the resulting output:
-\li Modify the asset path that is written back into the layer
-\li Add additional dependencies to be included in the output
-\li Remove an asset path and/or dependencies altogether from the output
+\li Modify an asset path to contain a new value.
+\li Add additional dependencies to be included in the output.
+\li Remove an asset path and/or dependencies altogether from the output.
Creating a processing function allows you to create customized output without
having to write dependency discovery and traversal code.
\endcode
After a processing function has been called, the system looks at the returned
-asset path. If it is empty, then the reference to that that path is removed,
+asset path. If it is empty, then the reference to that path is removed,
otherwise the new value is placed in the layer. Additionally, each item in the
dependencies array is added to the resulting package and enqueued for
recursive traversal.
# language governing permissions and limitations under the Apache License.
from pxr import UsdUtils, Sdf, Usd
+from pathlib import Path
import os
import unittest
def test_ComputeAllDependencies(self):
"""Basic test for UsdUtils.ComputeAllDependencies"""
- def _testLayer(rootLayer):
+ def _testLayer(rootLayer, processingFunc):
layers, assets, unresolved = \
- UsdUtils.ComputeAllDependencies(rootLayer)
+ UsdUtils.ComputeAllDependencies(rootLayer, processingFunc)
self.assertEqual(
set(layers),
"v_attr_a_nonexist.txt",
"v_attr_nonexist.usd"]]))
- def _test(rootLayer):
- _testLayer(rootLayer)
+ def _test(rootLayer, processingFunc = None):
+ _testLayer(rootLayer, processingFunc)
layer = Sdf.Layer.FindOrOpen(rootLayer)
layer.SetPermissionToEdit(False)
- _testLayer(rootLayer)
+ _testLayer(rootLayer, processingFunc)
_test("computeAllDependencies/ascii.usda")
_test("computeAllDependencies/ascii.usd")
_test("computeAllDependencies/crate.usdc")
_test("computeAllDependencies/crate.usd")
+ # test identity processing func
+ _test("computeAllDependencies/ascii.usda", lambda _, info: info)
+
def test_ComputeAllDependenciesInvalidClipTemplate(self):
"""Test that an invalid clip template asset path does not
cause an exception in UsdUtils.ComputeAllDependencies."""
Sdf.Layer.Find("anon_sublayer.usda")])
self.assertEqual(unresolved, ["unresolved.usda"])
+ def test_ComputeAllDependenciesUserFuncFilterPaths(self):
+ """Tests paths that are filtered by the processing func
+ do not appear in results"""
+
+ stagePath = "test_filter.usda"
+ assetDirPath = "./asset_dep_dir"
+ assetFilePath = "./non_dir_dep.usda"
+
+ if not os.path.exists(assetDirPath): os.mkdir(assetDirPath)
+ assetDepLayer = Sdf.Layer.CreateNew(assetFilePath)
+ assetDepLayer.Save()
+
+ stage = Usd.Stage.CreateNew(stagePath)
+ prim = stage.DefinePrim("/test")
+ dirAttr = prim.CreateAttribute("dirAsset", Sdf.ValueTypeNames.Asset)
+ dirAttr.Set(assetDirPath)
+ nonDirAttr = prim.CreateAttribute("depAsset", Sdf.ValueTypeNames.Asset)
+ nonDirAttr.Set(assetFilePath)
+ stage.GetRootLayer().Save()
+
+ def FilterDirectories(layer, depInfo):
+ if (os.path.isdir(depInfo.assetPath)):
+ return UsdUtils.DependencyInfo()
+ else:
+ return depInfo
+
+ layers, references, unresolved = \
+ UsdUtils.ComputeAllDependencies(stagePath, FilterDirectories)
+
+ self.assertEqual(layers, [stage.GetRootLayer(), assetDepLayer])
+ self.assertEqual(references, [])
+ self.assertEqual(unresolved, [])
+
+ def test_ComputeAllDependenciesUserFuncAdditionalPaths(self):
+ """Tests additional paths that are specified by the user processing func
+ appear in results"""
+
+ stagePath = "test_additional_deps.usda"
+ assetPath = "additional_dep.txt"
+ assetPathDep = "additional_dep.txt2"
+ Path(assetPath).touch()
+ Path(assetPathDep).touch()
+ stage = Usd.Stage.CreateNew(stagePath)
+ prim = stage.DefinePrim("/test")
+ attr = prim.CreateAttribute("depAsset", Sdf.ValueTypeNames.Asset)
+ attr.Set(assetPath)
+ stage.GetRootLayer().Save()
+
+ def AddAdditionalDeps(layer, depInfo):
+ return UsdUtils.DependencyInfo(
+ depInfo.assetPath, [depInfo.assetPath + "2"])
+
+
+ layers, references, unresolved = \
+ UsdUtils.ComputeAllDependencies(stagePath, AddAdditionalDeps)
+
+ self.assertEqual(layers, [stage.GetRootLayer()])
+ self.assertEqual([os.path.normcase(f) for f in references],
+ [os.path.normcase(os.path.abspath(assetPath)),
+ os.path.normcase(os.path.abspath(assetPathDep))])
+ self.assertEqual(unresolved, [])
+
+ def test_ComputeAllDependenciesUserFuncModifyPathss(self):
+ """Tests assets paths which are modified by the processing func
+ appear correctly in returned vectors"""
+
+ stagePath = "test_modified_deps.usda"
+ assetPath = "modified_dep.txt"
+ Path(assetPath).touch()
+ stage = Usd.Stage.CreateNew(stagePath)
+ prim = stage.DefinePrim("/test")
+ attr = prim.CreateAttribute("depAsset", Sdf.ValueTypeNames.Asset)
+ attr.Set("dep.txt")
+ stage.GetRootLayer().Save()
+
+ def ModifyDeps(layer, depInfo):
+ return UsdUtils.DependencyInfo("modified_" + depInfo.assetPath)
+
+
+ layers, references, unresolved = \
+ UsdUtils.ComputeAllDependencies(stagePath, ModifyDeps)
+
+ self.assertEqual(layers, [stage.GetRootLayer()])
+ self.assertEqual([os.path.normcase(f) for f in references],
+ [os.path.normcase(os.path.abspath(assetPath))])
+ self.assertEqual(unresolved, [])
+
+ def test_ComputeAllDependenciesParseAdditionalLayers(self):
+ """Tests that layers that are specified as additional dependencies are
+ themselves processed for additional assets"""
+
+ def CreateStageWithDep(stagePath, depPath):
+ stage = Usd.Stage.CreateNew(stagePath)
+ prim = stage.DefinePrim("/test")
+ if depPath is not None:
+ attr = prim.CreateAttribute("depAsset", Sdf.ValueTypeNames.Asset)
+ attr.Set(depPath)
+ stage.GetRootLayer().Save()
+ return stage
+
+ stagePath = "test_process_deps.usda"
+ asset = CreateStageWithDep(stagePath, "dep.usda")
+ dep = CreateStageWithDep("dep.usda", None)
+ extra = CreateStageWithDep("extra_dep.usda", "file.txt")
+ Path("file.txt").touch()
+
+ def AddExtra(layer, depInfo):
+ if depInfo.assetPath.startswith("dep"):
+ return UsdUtils.DependencyInfo(
+ depInfo.assetPath, ["extra_" + depInfo.assetPath])
+ else:
+ return depInfo
+
+ layers, references, unresolved = \
+ UsdUtils.ComputeAllDependencies(stagePath, AddExtra)
+
+ self.assertEqual(layers, [asset.GetRootLayer(), dep.GetRootLayer(),
+ extra.GetRootLayer()])
+ self.assertEqual([os.path.normcase(f) for f in references],
+ [os.path.normcase(os.path.abspath("file.txt"))])
+ self.assertEqual(unresolved, [])
+
+
if __name__=="__main__":
unittest.main()
: _assetPath(assetPath), _dependencies(dependencies) {}
/// Returns the asset value path for the dependency.
- /// When returned as a parameter from a user processing function, This value
- /// is checked by the localization system to see if it differs from the
- /// original authored value. If the returned value is set to and empty
- /// string, the asset will be removed from the layer and not included in the
- /// resulting package. If this value differs from what what was originally
- /// authored into the layer, the path will be updated with this new value.
- /// Additionally, the newly specified asset will be included in the package
- /// and searched for additional dependencies if it can be opened as a layer.
+ /// When UsdUtilsDependencyInfo is returned as a parameter from a user
+ /// processing function, the localization system compares the value
+ /// with the value that was originally authored in the layer.
+ ///
+ /// If the values are the same, no special action is taken and processing
+ /// will continue as normal.
+ ///
+ /// If the returned value is an empty string, the system will ignore this
+ /// path as well as any dependencies associated with it.
+ ///
+ /// If the returned value differs from what what was originally
+ /// authored into the layer, the system will instead operate on the updated.
+ /// value. If the updated path can be opened as a layer, it will be
+ /// enqueued and searched for additional dependencies.
USDUTILS_API const std::string& GetAssetPath() const {
return _assetPath;
}
/// When passed into the user processing function, if this array is
/// populated, then the asset path resolved to one or more values, such as
/// in the case of UDIM tiles or clip asset path template strings.
- /// When this structure is returned from a processing function, the paths
- /// contained within will be included in packaged output in addition to the
- /// value specified in the dependency's asset path. Any paths that can be
- /// opened as layers will be recursively searched for further dependencies.
+ ///
+ /// When this structure is returned from a processing function, each path
+ /// contained within will in turn be processed by the system. Any path
+ /// that can be opened as a layer, will be enqueued and searched for
+ /// additional dependencies.
USDUTILS_API const std::vector<std::string>& GetDependencies() const {
return _dependencies;
}
+ /// Equality: Asset path and dependencies are the same
+ bool operator==(const UsdUtilsDependencyInfo &rhs) const {
+ return _assetPath == rhs._assetPath &&
+ _dependencies == rhs._dependencies;
+ }
+
+ /// Inequality operator
+ /// \sa UsdUtilsDependencyInfo::operator==(const UsdUtilsDependencyInfo&)
+ bool operator!=(const UsdUtilsDependencyInfo& rhs) const {
+ return !(*this == rhs);
+ }
+
private:
std::string _assetPath;
std::vector<std::string> _dependencies;
};
/// Signature for user supplied processing function.
-/// \param layer The layer containing this dependency
-/// \param dependencyInfo contains asset path information for this dependency
+/// \param layer The layer containing this dependency.
+/// \param dependencyInfo contains asset path information for this dependency.
using UsdUtilsProcessingFunc = UsdUtilsDependencyInfo(
const SdfLayerHandle &layer,
const UsdUtilsDependencyInfo &dependencyInfo);
}
static bp::tuple
-_ComputeAllDependencies(const SdfAssetPath &assetPath)
+_ComputeAllDependencies(
+ const SdfAssetPath &assetPath,
+ std::function<UsdUtilsProcessingFunc> processingFunc)
{
std::vector<SdfLayerRefPtr> layers;
std::vector<std::string> assets, unresolvedPaths;
UsdUtilsComputeAllDependencies(assetPath, &layers, &assets,
- &unresolvedPaths);
+ &unresolvedPaths, processingFunc);
bp::list layersList;
for (auto &l: layers) {
layersList.append(_LayerRefToObj(l));
bp::arg("editLayersInPlace") = false));
bp::def("ComputeAllDependencies", _ComputeAllDependencies,
- (bp::arg("assetPath")));
+ (bp::arg("assetPath"),
+ bp::arg("processingFunc") = bp::object()));
using Py_UsdUtilsModifyAssetPathFn = std::string(const std::string&);
TfPyFunctionFromPython<Py_UsdUtilsModifyAssetPathFn>();