Skip to content

Commit

Permalink
Merge pull request #3444 from beersandrew/validation-usdUtils-FileExt…
Browse files Browse the repository at this point in the history
…ensionValidator

usdValidation: usdUtils: FileExtensionValidator

(Internal change: 2351433)
  • Loading branch information
pixar-oss committed Dec 12, 2024
2 parents 8c2cd47 + 0715bd7 commit 809e489
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
Keywords: UsdSkelValidators
SchemaTypes: UsdSkelBindingAPI
isSuite: False
[usdUtilsValidators: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: UsdUtilsValidators, UsdzValidators
isSuite: False
[usdUtilsValidators: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: UsdUtilsValidators, UsdzValidators
Expand Down
6 changes: 6 additions & 0 deletions pxr/usdValidation/usdUtilsValidators/plugInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
{
"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"
]
},
"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": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ TestUsdUsdzValidators()
UsdValidationValidatorMetadataVector metadata
= registry.GetValidatorMetadataForPlugin(
_tokens->usdUtilsValidatorsPlugin);
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.
Expand All @@ -40,11 +40,27 @@ TestUsdUsdzValidators()
}

const std::set<TfToken> expectedValidatorNames
= { UsdUtilsValidatorNameTokens->packageEncapsulationValidator };
= { UsdUtilsValidatorNameTokens->packageEncapsulationValidator,
UsdUtilsValidatorNameTokens->fileExtensionValidator };

TF_AXIOM(validatorMetadataNameSet == expectedValidatorNames);
}


void ValidateError(const UsdValidationError &error,
const std::string& expectedErrorMsg,
const TfToken& expectedErrorIdentifier,
UsdValidationErrorType expectedErrorType =
UsdValidationErrorType::Error)
{
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
TestPackageEncapsulationValidator()
{
Expand All @@ -60,56 +76,87 @@ TestPackageEncapsulationValidator()
// that are not included in the package, but exist
const UsdStageRefPtr &stage = UsdStage::Open("fail.usdz");

UsdValidationErrorVector errors = validator->Validate(stage);

// Verify both the layer & asset errors are present
TF_AXIOM(errors.size() == 2);

// Note that we keep the referenced layer in normalized path to represent
// the layer identifier, whereas the asset path is platform specific path,
// as returned by UsdUtilsComputeAllDependencies
const SdfLayerRefPtr &rootLayer = stage->GetRootLayer();
const std::string &rootLayerIdentifier = rootLayer->GetIdentifier();
const std::string realUsdzPath = rootLayer->GetRealPath();
const std::string errorLayer
= TfStringCatPaths(TfGetPathName(TfAbsPath(rootLayerIdentifier)),
"excludedDirectory/layer.usda");

std::filesystem::path parentDir
= std::filesystem::path(realUsdzPath).parent_path();
const std::string errorAsset
= (parentDir / "excludedDirectory" / "image.jpg").string();

std::array<std::string, 2> expectedErrorMessages
= { TfStringPrintf(("Found referenced layer '%s' that does not "
"belong to the package '%s'."),
errorLayer.c_str(), realUsdzPath.c_str()),
TfStringPrintf(("Found asset reference '%s' that does not belong"
" to the package '%s'."),
errorAsset.c_str(), realUsdzPath.c_str()) };

std::array<TfToken, 2> expectedErrorIdentifiers = {
TfToken(
"usdUtilsValidators:PackageEncapsulationValidator.LayerNotInPackage"),
TfToken(
"usdUtilsValidators:PackageEncapsulationValidator.AssetNotInPackage")
};

for (size_t i = 0; i < errors.size(); ++i) {
TF_AXIOM(errors[i].GetIdentifier() == expectedErrorIdentifiers[i]);
TF_AXIOM(errors[i].GetType() == UsdValidationErrorType::Warn);
TF_AXIOM(errors[i].GetSites().size() == 1);
TF_AXIOM(!errors[i].GetSites()[0].GetLayer().IsInvalid());
TF_AXIOM(errors[i].GetMessage() == expectedErrorMessages[i]);
{
const UsdValidationErrorVector errors = validator->Validate(stage);

// Verify both the layer & asset errors are present
TF_AXIOM(errors.size() == 2u);

// Note that we keep the referenced layer in normalized path to represent
// the layer identifier, whereas the asset path is platform specific path,
// as returned by UsdUtilsComputeAllDependencies
const SdfLayerRefPtr &rootLayer = stage->GetRootLayer();
const std::string &rootLayerIdentifier = rootLayer->GetIdentifier();
const std::string realUsdzPath = rootLayer->GetRealPath();
const std::string errorLayer
= TfStringCatPaths(TfGetPathName(TfAbsPath(rootLayerIdentifier)),
"excludedDirectory/layer.usda");

std::filesystem::path parentDir
= std::filesystem::path(realUsdzPath).parent_path();
const std::string errorAsset
= (parentDir / "excludedDirectory" / "image.jpg").string();

std::array<std::string, 2> expectedErrorMessages
= { TfStringPrintf(("Found referenced layer '%s' that does not "
"belong to the package '%s'."),
errorLayer.c_str(), realUsdzPath.c_str()),
TfStringPrintf(("Found asset reference '%s' that does not belong"
" to the package '%s'."),
errorAsset.c_str(), realUsdzPath.c_str()) };

std::array<TfToken, 2> expectedErrorIdentifiers = {
TfToken(
"usdUtilsValidators:PackageEncapsulationValidator.LayerNotInPackage"),
TfToken(
"usdUtilsValidators:PackageEncapsulationValidator.AssetNotInPackage")
};

for (size_t i = 0; i < errors.size(); ++i) {
ValidateError(errors[i], expectedErrorMessages[i],
expectedErrorIdentifiers[i], UsdValidationErrorType::Warn);
}
}

// 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");
{
const UsdValidationErrorVector errors = validator->Validate(passStage);

// Verify the errors are gone
TF_AXIOM(errors.empty());
}
}

static void
TestFileExtensionValidator()
{
UsdValidationRegistry& registry = UsdValidationRegistry::GetInstance();

// Verify the validator exists
const UsdValidationValidator *validator = registry.GetOrLoadValidatorByName(
UsdUtilsValidatorNameTokens->fileExtensionValidator);

TF_AXIOM(validator);

const UsdStageRefPtr& stage = UsdStage::Open("failFileExtension.usdz");
UsdValidationErrorVector errors = validator->Validate(stage);
TF_AXIOM(errors.size() == 1u);
const std::string expectedErrorMsg =
"File 'cube.invalid' in package 'failFileExtension.usdz' has an "
"unknown unsupported extension 'invalid'.";
const TfToken expectedErrorIdentifier(
"usdUtilsValidators:FileExtensionValidator.UnsupportedFileExtensionInPackage");
// Verify error occurs.
ValidateError(errors[0], expectedErrorMsg, expectedErrorIdentifier);

// Load a passing stage
const UsdStageRefPtr& passStage = UsdStage::Open("allSupportedFiles.usdz");

errors = validator->Validate(passStage);

// Verify the errors are gone
// Verify no errors occur with all valid extensions included
TF_AXIOM(errors.empty());
}

Expand All @@ -118,6 +165,7 @@ main()
{
TestUsdUsdzValidators();
TestPackageEncapsulationValidator();
TestFileExtensionValidator();

return EXIT_SUCCESS;
}
Binary file not shown.
Binary file not shown.
8 changes: 6 additions & 2 deletions pxr/usdValidation/usdUtilsValidators/validatorTokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ PXR_NAMESPACE_OPEN_SCOPE

#define USD_UTILS_VALIDATOR_NAME_TOKENS \
((packageEncapsulationValidator, \
"usdUtilsValidators:PackageEncapsulationValidator"))
"usdUtilsValidators:PackageEncapsulationValidator")) \
((fileExtensionValidator, \
"usdUtilsValidators:FileExtensionValidator"))

#define USD_UTILS_VALIDATOR_KEYWORD_TOKENS \
(UsdUtilsValidators) \
Expand All @@ -27,7 +29,9 @@ PXR_NAMESPACE_OPEN_SCOPE
#define USD_UTILS_VALIDATION_ERROR_NAME_TOKENS \
((layerNotInPackage, "LayerNotInPackage")) \
((assetNotInPackage, "AssetNotInPackage")) \
((invalidLayerInPackage, "InvalidLayerInPackage"))
((invalidLayerInPackage, "InvalidLayerInPackage")) \
((unsupportedFileExtensionInPackage, \
"UnsupportedFileExtensionInPackage"))

///\def
/// Tokens representing validator names. Note that for plugin provided
Expand Down
51 changes: 51 additions & 0 deletions pxr/usdValidation/usdUtilsValidators/validators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//

#include "pxr/usd/ar/packageUtils.h"
#include "pxr/usd/ar/resolver.h"
#include "pxr/usd/usd/zipFile.h"
#include "pxr/usd/usdUtils/dependencies.h"
#include "pxr/usd/usdUtils/userProcessingFunc.h"
#include "pxr/usdValidation/usdUtilsValidators/validatorTokens.h"
Expand Down Expand Up @@ -97,12 +99,61 @@ _PackageEncapsulationValidator(const UsdStagePtr &usdStage)
return errors;
}

static UsdValidationErrorVector
_FileExtensionValidator(const UsdStagePtr& usdStage) {
UsdValidationErrorVector errors;

const std::set<TfToken> validExtensions = {TfToken("usda"),
TfToken("usdc"), TfToken("usd"), TfToken("usdz"), TfToken("png"),
TfToken("jpg"), TfToken("jpeg"), TfToken("exr"), TfToken("avif"),
TfToken("m4a"), TfToken("mp3"), TfToken("wav")};

const SdfLayerHandle& rootLayer = usdStage->GetRootLayer();
const UsdZipFile& zipFile = UsdZipFile::Open(rootLayer->GetRealPath());

const std::vector<std::string> fileNames =
std::vector<std::string>(zipFile.begin(), zipFile.end());

for (const std::string& fileName : fileNames)
{
const std::string extension = ArGetResolver().GetExtension(fileName);

if (std::find(validExtensions.begin(), validExtensions.end(),
extension) == validExtensions.end())
{
return {
UsdValidationError {
UsdUtilsValidationErrorNameTokens->
unsupportedFileExtensionInPackage,
UsdValidationErrorType::Error,
UsdValidationErrorSites {
UsdValidationErrorSite(
rootLayer, SdfPath(rootLayer->GetIdentifier()))
},
TfStringPrintf("File '%s' in package '%s' has an unknown "
"unsupported extension '%s'.",
fileName.c_str(),
rootLayer->GetIdentifier().c_str(),
extension.c_str())
}
};
}
}

return errors;
}

TF_REGISTRY_FUNCTION(UsdValidationRegistry)
{
UsdValidationRegistry &registry = UsdValidationRegistry::GetInstance();

registry.RegisterPluginValidator(
UsdUtilsValidatorNameTokens->packageEncapsulationValidator,
_PackageEncapsulationValidator);

registry.RegisterPluginValidator(
UsdUtilsValidatorNameTokens->fileExtensionValidator,
_FileExtensionValidator);
}

PXR_NAMESPACE_CLOSE_SCOPE

0 comments on commit 809e489

Please sign in to comment.