From dedd4130a14c9bed284e983836a180aeb9e9a7df Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 2 Dec 2024 22:47:33 -0500 Subject: [PATCH 1/4] usdValidation: usdUtils: MissingReferenceValidator created implementation, test, as well as associated entries in plugInfo.json & validatorTokens.h --- .../usdUtilsValidators/plugInfo.json | 11 +++-- .../testenv/testUsdUtilsValidators.cpp | 49 ++++++++++++++++++- .../usdUtilsValidators/validatorTokens.h | 22 +++++---- .../usdUtilsValidators/validators.cpp | 34 +++++++++++++ 4 files changed, 101 insertions(+), 15 deletions(-) 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..a635d3ae52 100644 --- a/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp +++ b/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp @@ -7,6 +7,7 @@ #include "pxr/base/arch/systemInfo.h" #include "pxr/base/tf/pathUtils.h" +#include "pxr/usd/usdGeom/xform.h" #include "pxr/usdValidation/usdUtilsValidators/validatorTokens.h" #include "pxr/usdValidation/usdValidation/error.h" #include "pxr/usdValidation/usdValidation/registry.h" @@ -30,7 +31,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,7 +42,8 @@ TestUsdUsdzValidators() const std::set expectedValidatorNames = { UsdUtilsValidatorNameTokens->packageEncapsulationValidator, - UsdUtilsValidatorNameTokens->fileExtensionValidator }; + UsdUtilsValidatorNameTokens->fileExtensionValidator, + UsdUtilsValidatorNameTokens->missingReferenceValidator }; TF_AXIOM(validatorMetadataNameSet == expectedValidatorNames); } @@ -160,12 +162,55 @@ 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); + + // 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( + "usdUtilsValidators: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(); 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); From a07917819df967154c75968c523047441a550e1c Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 12 Dec 2024 01:30:18 -0500 Subject: [PATCH 2/4] feat: adding an asset reference test --- .../testenv/testUsdUtilsValidators.cpp | 64 ++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp b/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp index a635d3ae52..b791510acb 100644 --- a/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp +++ b/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp @@ -48,19 +48,18 @@ TestUsdUsdzValidators() 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 @@ -173,32 +172,53 @@ TestMissingReferenceValidator() 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); + const SdfReference badLayerReference("doesNotExist.usd"); + xform.GetPrim().GetReferences().AddReference(badLayerReference); - UsdValidationErrorVector errors = validator->Validate(stage); + // Validate an error occurs on a missing layer reference + { + const UsdValidationErrorVector errors = validator->Validate(stage); + + // Verify both the layer errors are present + const std::string expectedErrorMessage = "Found unresolvable external " + "dependency 'doesNotExist.usd'."; + const TfToken expectedIdentifier = + TfToken( + "usdUtilsValidators:MissingReferenceValidator.UnresolvableDependency"); + TF_AXIOM(errors.size() == 1); + + ValidateError(errors[0], expectedErrorMessage, + expectedIdentifier); + } + + xform.GetPrim().GetReferences().RemoveReference(badLayerReference); + const SdfReference badAssetReference("doesNotExist.jpg"); + xform.GetPrim().GetReferences().AddReference(badAssetReference); + + // Validate an error occurs on a missing asset reference + { + const UsdValidationErrorVector errors = validator->Validate(stage); - // Verify both the layer & asset errors are present - const TfToken expectedIdentifier = - TfToken( - "usdUtilsValidators: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); + // Verify both the layer errors are present + const std::string expectedErrorMessage = "Found unresolvable external " + "dependency 'doesNotExist.jpg'."; + const TfToken expectedIdentifier = + TfToken( + "usdUtilsValidators:MissingReferenceValidator.UnresolvableDependency"); + TF_AXIOM(errors.size() == 1); + + ValidateError(errors[0], expectedErrorMessage, + expectedIdentifier); + } + + // Remove the nonexistent asset reference, add an existing reference + xform.GetPrim().GetReferences().RemoveReference(badAssetReference); xform.GetPrim().GetReferences().AddReference("pass.usdz"); - errors = validator->Validate(stage); + + const UsdValidationErrorVector errors = validator->Validate(stage); // Verify the errors are gone TF_AXIOM(errors.empty()); From 0fa86de81902af6e7db77b97d391fec2e38eefd3 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 12 Dec 2024 13:56:19 -0500 Subject: [PATCH 3/4] feat: added unused variant test --- .../testenv/testUsdUtilsValidators.cpp | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp b/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp index b791510acb..b5816b08a5 100644 --- a/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp +++ b/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp @@ -8,6 +8,7 @@ #include "pxr/base/arch/systemInfo.h" #include "pxr/base/tf/pathUtils.h" #include "pxr/usd/usdGeom/xform.h" +#include "pxr/usd/usd/variantSets.h" #include "pxr/usdValidation/usdUtilsValidators/validatorTokens.h" #include "pxr/usdValidation/usdValidation/error.h" #include "pxr/usdValidation/usdValidation/registry.h" @@ -213,9 +214,36 @@ TestMissingReferenceValidator() ValidateError(errors[0], expectedErrorMessage, expectedIdentifier); } + + xform.GetPrim().GetReferences().RemoveReference(badAssetReference); + // Set an unresolved dependency on an unused variant + + UsdVariantSets variantSets = xform.GetPrim().GetVariantSets(); + UsdVariantSet colorsVariantSet = variantSets.AddVariantSet("myVariant"); + + UsdGeomXform selectedXform = UsdGeomXform::Define(stage, xform.GetPath().AppendChild(TfToken("selected"))); + UsdGeomXform notSelectedXform = UsdGeomXform::Define(stage, xform.GetPath().AppendChild(TfToken("notSelected"))); + const SdfReference badAssetOnVariantReference("doesNotExistOnVariant.jpg"); + notSelectedXform.GetPrim().GetReferences().AddReference(badAssetOnVariantReference); + + // Validate an error occurs on a missing asset reference + { + const UsdValidationErrorVector errors = validator->Validate(stage); + + // Verify both the layer errors are present + const std::string expectedErrorMessage = "Found unresolvable external " + "dependency 'doesNotExistOnVariant.jpg'."; + const TfToken expectedIdentifier = + TfToken( + "usdUtilsValidators:MissingReferenceValidator.UnresolvableDependency"); + TF_AXIOM(errors.size() == 1); + + ValidateError(errors[0], expectedErrorMessage, + expectedIdentifier); + } // Remove the nonexistent asset reference, add an existing reference - xform.GetPrim().GetReferences().RemoveReference(badAssetReference); + notSelectedXform.GetPrim().GetReferences().RemoveReference(badAssetOnVariantReference); xform.GetPrim().GetReferences().AddReference("pass.usdz"); const UsdValidationErrorVector errors = validator->Validate(stage); From d71c0a149efc9c1e49e253bf90ecd659fc9bd2a4 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 18 Dec 2024 13:35:04 -0500 Subject: [PATCH 4/4] fix: address PR comments - remove unnecessary usage of usdGeom - corrected how the missing asset test was written - corrected how the variant test was written --- .../testenv/testUsdUtilsValidators.cpp | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp b/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp index b5816b08a5..14560cc8d6 100644 --- a/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp +++ b/pxr/usdValidation/usdUtilsValidators/testenv/testUsdUtilsValidators.cpp @@ -7,8 +7,9 @@ #include "pxr/base/arch/systemInfo.h" #include "pxr/base/tf/pathUtils.h" -#include "pxr/usd/usdGeom/xform.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" @@ -175,75 +176,84 @@ TestMissingReferenceValidator() const UsdStageRefPtr& stage = UsdStage::CreateInMemory(); - const UsdGeomXform xform = UsdGeomXform::Define(stage, SdfPath("/Xform")); + const TfToken xformToken("Xform"); + const UsdPrim xform = stage->DefinePrim(SdfPath("/Prim"), xformToken); const SdfReference badLayerReference("doesNotExist.usd"); - xform.GetPrim().GetReferences().AddReference(badLayerReference); + xform.GetReferences().AddReference(badLayerReference); - // Validate an error occurs on a missing layer reference + // Validate an error occurs for a missing layer reference { const UsdValidationErrorVector errors = validator->Validate(stage); - - // Verify both the layer errors are present + TF_AXIOM(errors.size() == 1); const std::string expectedErrorMessage = "Found unresolvable external " "dependency 'doesNotExist.usd'."; const TfToken expectedIdentifier = TfToken( "usdUtilsValidators:MissingReferenceValidator.UnresolvableDependency"); - TF_AXIOM(errors.size() == 1); ValidateError(errors[0], expectedErrorMessage, expectedIdentifier); } - xform.GetPrim().GetReferences().RemoveReference(badLayerReference); - const SdfReference badAssetReference("doesNotExist.jpg"); - xform.GetPrim().GetReferences().AddReference(badAssetReference); + // 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 on a missing asset reference + // Validate an error occurs for a missing asset reference { const UsdValidationErrorVector errors = validator->Validate(stage); - - // Verify both the layer errors are present + TF_AXIOM(errors.size() == 1); const std::string expectedErrorMessage = "Found unresolvable external " - "dependency 'doesNotExist.jpg'."; + "dependency 'doesNotExist.jpg'."; const TfToken expectedIdentifier = TfToken( "usdUtilsValidators:MissingReferenceValidator.UnresolvableDependency"); - TF_AXIOM(errors.size() == 1); ValidateError(errors[0], expectedErrorMessage, expectedIdentifier); } - xform.GetPrim().GetReferences().RemoveReference(badAssetReference); - // Set an unresolved dependency on an unused variant + // 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"); - UsdVariantSets variantSets = xform.GetPrim().GetVariantSets(); - UsdVariantSet colorsVariantSet = variantSets.AddVariantSet("myVariant"); + testVariantSet.AddVariant("invalid"); + testVariantSet.AddVariant("valid"); - UsdGeomXform selectedXform = UsdGeomXform::Define(stage, xform.GetPath().AppendChild(TfToken("selected"))); - UsdGeomXform notSelectedXform = UsdGeomXform::Define(stage, xform.GetPath().AppendChild(TfToken("notSelected"))); - const SdfReference badAssetOnVariantReference("doesNotExistOnVariant.jpg"); - notSelectedXform.GetPrim().GetReferences().AddReference(badAssetOnVariantReference); + testVariantSet.SetVariantSelection("invalid"); - // Validate an error occurs on a missing asset reference { - const UsdValidationErrorVector errors = validator->Validate(stage); + 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"); - // Verify both the layer errors are present + // 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"); - TF_AXIOM(errors.size() == 1); ValidateError(errors[0], expectedErrorMessage, expectedIdentifier); } - - // Remove the nonexistent asset reference, add an existing reference - notSelectedXform.GetPrim().GetReferences().RemoveReference(badAssetOnVariantReference); + + // 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);