diff --git a/pxr/usdValidation/usdUtilsValidators/plugInfo.json b/pxr/usdValidation/usdUtilsValidators/plugInfo.json index 5bca0d4eb2..cb5a7c23a1 100644 --- a/pxr/usdValidation/usdUtilsValidators/plugInfo.json +++ b/pxr/usdValidation/usdUtilsValidators/plugInfo.json @@ -4,10 +4,13 @@ "Info": { "Validators": { "FileExtensionValidator": { - "doc": "Only valid core layer extensions (.usd, .usda, .usdc, .usdz), valid core texture extensions (.exr, .jpg, .jpeg, .png) and embedded audio files (.M4A, .MP3, .WAV) are allowed in a package.", - "keywords": [ - "UsdzValidators" - ] + "doc": "Only valid core layer extensions (.usd, .usda, .usdc, .usdz), valid core texture extensions (.exr, .jpg, .jpeg, .png) and embedded audio files (.M4A, .MP3, .WAV) are allowed in a package.", + "keywords": [ + "UsdzValidators" + ] + }, + "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.", diff --git a/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp b/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp index 162a278ec6..14560cc8d6 100644 --- a/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp +++ b/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp @@ -7,6 +7,9 @@ #include "pxr/base/arch/systemInfo.h" #include "pxr/base/tf/pathUtils.h" +#include "pxr/usd/usd/editContext.h" +#include "pxr/usd/usd/variantSets.h" +#include "pxr/usd/usdShade/shader.h" #include "pxr/usdValidation/usdUtilsValidators/validatorTokens.h" #include "pxr/usdValidation/usdValidation/error.h" #include "pxr/usdValidation/usdValidation/registry.h" @@ -30,7 +33,7 @@ TestUsdUsdzValidators() UsdValidationValidatorMetadataVector metadata = registry.GetValidatorMetadataForPlugin( _tokens->usdUtilsValidatorsPlugin); - TF_AXIOM(metadata.size() == 2); + TF_AXIOM(metadata.size() == 3); // Since other validators can be registered with a UsdUtilsValidators // keyword, our validators registered in usd are a subset of the entire // set. @@ -41,24 +44,24 @@ TestUsdUsdzValidators() const std::set expectedValidatorNames = { UsdUtilsValidatorNameTokens->packageEncapsulationValidator, - UsdUtilsValidatorNameTokens->fileExtensionValidator }; + UsdUtilsValidatorNameTokens->fileExtensionValidator, + UsdUtilsValidatorNameTokens->missingReferenceValidator }; TF_AXIOM(validatorMetadataNameSet == expectedValidatorNames); } - void ValidateError(const UsdValidationError &error, const std::string& expectedErrorMsg, const TfToken& expectedErrorIdentifier, UsdValidationErrorType expectedErrorType = UsdValidationErrorType::Error) { + TF_AXIOM(error.GetMessage() == expectedErrorMsg); TF_AXIOM(error.GetIdentifier() == expectedErrorIdentifier); TF_AXIOM(error.GetType() == expectedErrorType); TF_AXIOM(error.GetSites().size() == 1u); const UsdValidationErrorSite &errorSite = error.GetSites()[0]; TF_AXIOM(!errorSite.GetLayer().IsInvalid()); - TF_AXIOM(error.GetMessage() == expectedErrorMsg); } static void @@ -160,12 +163,112 @@ TestFileExtensionValidator() TF_AXIOM(errors.empty()); } +static void +TestMissingReferenceValidator() +{ + UsdValidationRegistry& registry = UsdValidationRegistry::GetInstance(); + + // Verify the validator exists + const UsdValidationValidator *validator = registry.GetOrLoadValidatorByName( + UsdUtilsValidatorNameTokens->missingReferenceValidator); + + TF_AXIOM(validator); + + const UsdStageRefPtr& stage = UsdStage::CreateInMemory(); + + const TfToken xformToken("Xform"); + const UsdPrim xform = stage->DefinePrim(SdfPath("/Prim"), xformToken); + const SdfReference badLayerReference("doesNotExist.usd"); + xform.GetReferences().AddReference(badLayerReference); + + // Validate an error occurs for a missing layer reference + { + const UsdValidationErrorVector errors = validator->Validate(stage); + TF_AXIOM(errors.size() == 1); + const std::string expectedErrorMessage = "Found unresolvable external " + "dependency 'doesNotExist.usd'."; + const TfToken expectedIdentifier = + TfToken( + "usdUtilsValidators:MissingReferenceValidator.UnresolvableDependency"); + + ValidateError(errors[0], expectedErrorMessage, + expectedIdentifier); + } + + // Remove the bad layer reference and add a shader prim for the next test + xform.GetReferences().RemoveReference(badLayerReference); + UsdShadeShader shader = UsdShadeShader::Define(stage, + SdfPath("/Prim/Shader")); + const TfToken notFoundAsset("notFoundAsset"); + UsdShadeInput notFoundAssetInput = shader.CreateInput( + notFoundAsset, SdfValueTypeNames->Asset); + notFoundAssetInput.Set(SdfAssetPath("doesNotExist.jpg")); + + // Validate an error occurs for a missing asset reference + { + const UsdValidationErrorVector errors = validator->Validate(stage); + TF_AXIOM(errors.size() == 1); + const std::string expectedErrorMessage = "Found unresolvable external " + "dependency 'doesNotExist.jpg'."; + const TfToken expectedIdentifier = + TfToken( + "usdUtilsValidators:MissingReferenceValidator.UnresolvableDependency"); + + ValidateError(errors[0], expectedErrorMessage, + expectedIdentifier); + } + + // Remove shader prim and add a variant set for the next test + stage->RemovePrim(shader.GetPath()); + UsdVariantSets variantSets = xform.GetVariantSets(); + UsdVariantSet testVariantSet = variantSets.AddVariantSet("testVariantSet"); + + testVariantSet.AddVariant("invalid"); + testVariantSet.AddVariant("valid"); + + testVariantSet.SetVariantSelection("invalid"); + + { + UsdEditContext context(testVariantSet.GetVariantEditContext()); + UsdShadeShader shader = UsdShadeShader::Define(stage, xform.GetPath().AppendChild(TfToken("Shader"))); + UsdShadeInput invalidAssetInput = shader.CreateInput( + TfToken("invalidAsset"), SdfValueTypeNames->Asset); + invalidAssetInput.Set(SdfAssetPath("doesNotExistOnVariant.jpg")); + } + testVariantSet.SetVariantSelection("valid"); + + // Validate an error occurs on a nonexistent asset on an inactive variant + { + const UsdValidationErrorVector errors = validator->Validate(stage); + TF_AXIOM(errors.size() == 1); + const std::string expectedErrorMessage = "Found unresolvable external " + "dependency 'doesNotExistOnVariant.jpg'."; + const TfToken expectedIdentifier = + TfToken( + "usdUtilsValidators:MissingReferenceValidator.UnresolvableDependency"); + + ValidateError(errors[0], expectedErrorMessage, + expectedIdentifier); + } + + // Remove the variant set and add a valid reference + SdfPrimSpecHandle primSpec = stage->GetRootLayer()->GetPrimAtPath(xform.GetPath()); + primSpec->RemoveVariantSet("testVariantSet"); + xform.GetPrim().GetReferences().AddReference("pass.usdz"); + + const UsdValidationErrorVector errors = validator->Validate(stage); + + // Verify the errors are gone + TF_AXIOM(errors.empty()); +} + int main() { TestUsdUsdzValidators(); TestPackageEncapsulationValidator(); TestFileExtensionValidator(); + TestMissingReferenceValidator(); return EXIT_SUCCESS; } diff --git a/pxr/usdValidation/usdUtilsValidators/validatorTokens.h b/pxr/usdValidation/usdUtilsValidators/validatorTokens.h index 1ae79a3383..299751aa8c 100644 --- a/pxr/usdValidation/usdUtilsValidators/validatorTokens.h +++ b/pxr/usdValidation/usdUtilsValidators/validatorTokens.h @@ -16,14 +16,16 @@ PXR_NAMESPACE_OPEN_SCOPE -#define USD_UTILS_VALIDATOR_NAME_TOKENS \ - ((packageEncapsulationValidator, \ - "usdUtilsValidators:PackageEncapsulationValidator")) \ - ((fileExtensionValidator, \ - "usdUtilsValidators:FileExtensionValidator")) - -#define USD_UTILS_VALIDATOR_KEYWORD_TOKENS \ - (UsdUtilsValidators) \ +#define USD_UTILS_VALIDATOR_NAME_TOKENS \ + ((packageEncapsulationValidator, \ + "usdUtilsValidators:PackageEncapsulationValidator")) \ + ((fileExtensionValidator, \ + "usdUtilsValidators:FileExtensionValidator")) \ + ((missingReferenceValidator, \ + "usdUtilsValidators:MissingReferenceValidator")) \ + +#define USD_UTILS_VALIDATOR_KEYWORD_TOKENS \ + (UsdUtilsValidators) \ (UsdzValidators) #define USD_UTILS_VALIDATION_ERROR_NAME_TOKENS \ @@ -31,7 +33,9 @@ PXR_NAMESPACE_OPEN_SCOPE ((assetNotInPackage, "AssetNotInPackage")) \ ((invalidLayerInPackage, "InvalidLayerInPackage")) \ ((unsupportedFileExtensionInPackage, \ - "UnsupportedFileExtensionInPackage")) + "UnsupportedFileExtensionInPackage")) \ + ((unresolvableDependency, "UnresolvableDependency")) + ///\def /// Tokens representing validator names. Note that for plugin provided diff --git a/pxr/usdValidation/usdUtilsValidators/validators.cpp b/pxr/usdValidation/usdUtilsValidators/validators.cpp index 204274ab43..b69ba243f1 100644 --- a/pxr/usdValidation/usdUtilsValidators/validators.cpp +++ b/pxr/usdValidation/usdUtilsValidators/validators.cpp @@ -143,10 +143,44 @@ _FileExtensionValidator(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 ®istry = UsdValidationRegistry::GetInstance(); + registry.RegisterPluginValidator( + UsdUtilsValidatorNameTokens->missingReferenceValidator, _MissingReferenceValidator); + registry.RegisterPluginValidator( UsdUtilsValidatorNameTokens->packageEncapsulationValidator, _PackageEncapsulationValidator);