diff --git a/pxr/usd/usdUtils/plugInfo.json b/pxr/usd/usdUtils/plugInfo.json index 1962a094a6..d26d50cb41 100644 --- a/pxr/usd/usdUtils/plugInfo.json +++ b/pxr/usd/usdUtils/plugInfo.json @@ -3,6 +3,9 @@ { "Info": { "Validators": { + "MissingReferenceValidator": { + "doc": "The composed USD stage should not contain any unresolvable asset dependencies (in every possible variation of the asset), when using the default asset resolver." + }, "PackageEncapsulationValidator": { "doc": "If the root layer is a package, then its recommended for the composed stage to not contain references to files outside the package. The package should be self-contained, warn if not.", "keywords": [ diff --git a/pxr/usd/usdUtils/testenv/testUsdUtilsValidators.cpp b/pxr/usd/usdUtils/testenv/testUsdUtilsValidators.cpp index 165b8e917f..787b8424a1 100644 --- a/pxr/usd/usdUtils/testenv/testUsdUtilsValidators.cpp +++ b/pxr/usd/usdUtils/testenv/testUsdUtilsValidators.cpp @@ -7,9 +7,9 @@ #include "pxr/usd/usd/validator.h" #include "pxr/usd/usd/validationError.h" +#include "pxr/usd/usdGeom/xform.h" #include "pxr/usd/usdUtils/validatorTokens.h" #include "pxr/usd/usd/validationRegistry.h" -#include "pxr/base/arch/systemInfo.h" #include "pxr/base/tf/pathUtils.h" #include @@ -30,7 +30,7 @@ TestUsdUsdzValidators() UsdValidationRegistry& registry = UsdValidationRegistry::GetInstance(); UsdValidatorMetadataVector metadata = registry.GetValidatorMetadataForPlugin(_tokens->usdUtilsPlugin); - TF_AXIOM(metadata.size() == 1); + TF_AXIOM(metadata.size() == 2); // Since other validators can be registered with a UsdUtilsValidators // keyword, our validators registered in usd are a subset of the entire // set. @@ -40,7 +40,8 @@ TestUsdUsdzValidators() } const std::set expectedValidatorNames = - {UsdUtilsValidatorNameTokens->packageEncapsulationValidator}; + {UsdUtilsValidatorNameTokens->packageEncapsulationValidator, + UsdUtilsValidatorNameTokens->missingReferenceValidator}; TF_AXIOM(validatorMetadataNameSet == expectedValidatorNames); } @@ -73,12 +74,12 @@ TestPackageEncapsulationValidator() const std::string& rootLayerIdentifier = rootLayer->GetIdentifier(); const std::string realUsdzPath = rootLayer->GetRealPath(); const std::string errorLayer = TfStringCatPaths( - TfGetPathName(TfAbsPath(rootLayerIdentifier)), + TfGetPathName(TfAbsPath(rootLayerIdentifier)), "excludedDirectory/layer.usda"); - std::filesystem::path parentDir = + std::filesystem::path parentDir = std::filesystem::path(realUsdzPath).parent_path(); - const std::string errorAsset = + const std::string errorAsset = (parentDir / "excludedDirectory" / "image.jpg").string(); std::array expectedErrorMessages = { @@ -107,18 +108,61 @@ TestPackageEncapsulationValidator() // Load the pre-created usdz stage with relative paths to both a reference // and an asset that are included in the package. const UsdStageRefPtr& passStage = UsdStage::Open("pass.usdz"); - + errors = validator->Validate(passStage); // Verify the errors are gone TF_AXIOM(errors.empty()); } +static +void +TestMissingReferenceValidator() +{ + UsdValidationRegistry& registry = UsdValidationRegistry::GetInstance(); + + // Verify the validator exists + const UsdValidator *validator = registry.GetOrLoadValidatorByName( + UsdUtilsValidatorNameTokens->missingReferenceValidator); + + TF_AXIOM(validator); + + // Create stage with a reference that does not exist + const UsdStageRefPtr& stage = UsdStage::CreateInMemory(); + + const UsdGeomXform xform = UsdGeomXform::Define(stage, SdfPath("/Xform")); + const SdfReference badReference("doesNotExist.usd"); + xform.GetPrim().GetReferences().AddReference(badReference); + + UsdValidationErrorVector errors = validator->Validate(stage); + + // Verify both the layer & asset errors are present + const TfToken expectedIdentifier = + TfToken("usdUtils:MissingReferenceValidator.UnresolvableDependency"); + TF_AXIOM(errors.size() == 1); + TF_AXIOM(errors[0].GetIdentifier() == expectedIdentifier); + TF_AXIOM(errors[0].GetType() == UsdValidationErrorType::Error); + TF_AXIOM(errors[0].GetSites().size() == 1); + TF_AXIOM(!errors[0].GetSites()[0].GetLayer().IsInvalid()); + const std::string expectedErrorMessage = "Found unresolvable external " + "dependency 'doesNotExist.usd'."; + TF_AXIOM(errors[0].GetMessage() == expectedErrorMessage); + + // Remove the nonexistent reference, add an existing reference + xform.GetPrim().GetReferences().RemoveReference(badReference); + xform.GetPrim().GetReferences().AddReference("pass.usdz"); + errors = validator->Validate(stage); + + // Verify the errors are gone + TF_AXIOM(errors.empty()); +} + int main() { TestUsdUsdzValidators(); TestPackageEncapsulationValidator(); + TestMissingReferenceValidator(); return EXIT_SUCCESS; } diff --git a/pxr/usd/usdUtils/validatorTokens.h b/pxr/usd/usdUtils/validatorTokens.h index cc0b7ac1df..4b9e0e5857 100644 --- a/pxr/usd/usdUtils/validatorTokens.h +++ b/pxr/usd/usdUtils/validatorTokens.h @@ -17,16 +17,18 @@ PXR_NAMESPACE_OPEN_SCOPE #define USD_UTILS_VALIDATOR_NAME_TOKENS \ + ((missingReferenceValidator, "usdUtils:MissingReferenceValidator")) \ ((packageEncapsulationValidator, "usdUtils:PackageEncapsulationValidator")) #define USD_UTILS_VALIDATOR_KEYWORD_TOKENS \ (UsdUtilsValidators) \ (UsdzValidators) -#define USD_UTILS_VALIDATION_ERROR_NAME_TOKENS \ - ((layerNotInPackage, "LayerNotInPackage")) \ - ((assetNotInPackage, "AssetNotInPackage")) \ - ((invalidLayerInPackage, "InvalidLayerInPackage")) +#define USD_UTILS_VALIDATION_ERROR_NAME_TOKENS \ + ((layerNotInPackage, "LayerNotInPackage")) \ + ((assetNotInPackage, "AssetNotInPackage")) \ + ((invalidLayerInPackage, "InvalidLayerInPackage")) \ + ((unresolvableDependency, "UnresolvableDependency")) \ ///\def /// Tokens representing validator names. Note that for plugin provided diff --git a/pxr/usd/usdUtils/validators.cpp b/pxr/usd/usdUtils/validators.cpp index 64e4cd33c3..aa6e8c0672 100644 --- a/pxr/usd/usdUtils/validators.cpp +++ b/pxr/usd/usdUtils/validators.cpp @@ -104,9 +104,44 @@ _PackageEncapsulationValidator(const UsdStagePtr& usdStage) { return errors; } +static +UsdValidationErrorVector +_MissingReferenceValidator(const UsdStagePtr& usdStage) { + const SdfLayerRefPtr& rootLayer = usdStage->GetRootLayer(); + + SdfLayerRefPtrVector layers; + std::vector> assets, unresolvedPaths; + const SdfAssetPath& path = SdfAssetPath(rootLayer->GetIdentifier()); + + UsdUtilsComputeAllDependencies(path, &layers, &assets, &unresolvedPaths, + nullptr); + + UsdValidationErrorVector errors; + for(const std::basic_string& unresolvedPath : unresolvedPaths) + { + errors.emplace_back( + UsdUtilsValidationErrorNameTokens->unresolvableDependency, + UsdValidationErrorType::Error, + UsdValidationErrorSites { + UsdValidationErrorSite( + rootLayer, SdfPath(unresolvedPath)) + }, + TfStringPrintf( + ("Found unresolvable external dependency " + "'%s'."), unresolvedPath.c_str()) + ); + } + + return errors; +} + TF_REGISTRY_FUNCTION(UsdValidationRegistry) { UsdValidationRegistry& registry = UsdValidationRegistry::GetInstance(); + + registry.RegisterPluginValidator( + UsdUtilsValidatorNameTokens->missingReferenceValidator, _MissingReferenceValidator); + registry.RegisterPluginValidator( UsdUtilsValidatorNameTokens->packageEncapsulationValidator, _PackageEncapsulationValidator); }