Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add validator SkelBindingAPIAppliedChecker #3166

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions pxr/usd/usdSkel/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pxr_library(usdSkel
tokens
topology
utils
validatorTokens

CPPFILES
validators.cpp

PUBLIC_HEADERS
api.h
Expand Down Expand Up @@ -93,6 +97,15 @@ pxr_library(usdSkel
images/influencesPrimvarLayout.svg
images/unboundedInterpolationExample.svg
)

pxr_build_test(testUsdSkelValidators
LIBRARIES
tf
usd
usdSkel
CPPFILES
testenv/testUsdSkelValidators.cpp
)

pxr_test_scripts(
testenv/testUsdSkelAnimMapper.py
Expand Down Expand Up @@ -189,3 +202,9 @@ pxr_register_test(testUsdSkelUtils
COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdSkelUtils"
EXPECTED_RETURN_CODE 0
)

pxr_register_test(testUsdSkelValidators
COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdSkelValidators"
EXPECTED_RETURN_CODE 0
)

8 changes: 8 additions & 0 deletions pxr/usd/usdSkel/plugInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@
"implementsComputeExtent": true,
"schemaKind": "concreteTyped"
}
},
"Validators": {
"SkelBindingApiAppliedValidator": {
"doc": "Verify a prim has the SkelBindingAPI applied if it has a UsdSkelBinding property. If a prim has the SkelBindingAPI applied, make sure it's of type SkelRoot or parented by a SkelRoot."
},
"keywords": [
"UsdSkelValidators"
]
}
},
"LibraryPath": "@PLUG_INFO_LIBRARY_PATH@",
Expand Down
107 changes: 107 additions & 0 deletions pxr/usd/usdSkel/testenv/testUsdSkelValidators.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// Copyright 2024 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//
#include "pxr/usd/usd/validationRegistry.h"
#include "pxr/usd/usd/validationError.h"
#include <pxr/usd/usdSkel/validatorTokens.h>
#include <pxr/usd/usdSkel/root.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/primvarsAPI.h>

PXR_NAMESPACE_USING_DIRECTIVE

void
TestUsdSkelValidators()
{
// This should be updated with every new validator added with
// UsdSkelValidators keyword.
UsdValidationRegistry &registry = UsdValidationRegistry::GetInstance();
UsdValidatorMetadataVector metadata =
registry.GetValidatorMetadataForKeyword(
UsdSkelValidatorKeywordTokens->UsdSkelValidators);
// Since other validators can be registered with a UsdSkelValidators
// keyword, our validators registered in usdSkel are a subset of the entire
// set.
std::set<TfToken> validatorMetadataNameSet;
for (const UsdValidatorMetadata &metadata : metadata) {
validatorMetadataNameSet.insert(metadata.name);
}

const std::set<TfToken> expectedValidatorNames =
{UsdSkelValidatorNameTokens->skelBindingApiAppliedValidator};

TF_AXIOM(std::includes(validatorMetadataNameSet.begin(),
validatorMetadataNameSet.end(),
expectedValidatorNames.begin(),
expectedValidatorNames.end()));
}

void
TestUsdSkelBindingApiAppliedValidator()
{
// Verify that the validator exists.
UsdValidationRegistry &registry = UsdValidationRegistry::GetInstance();
const UsdValidator *validator = registry.GetOrLoadValidatorByName(
UsdSkelValidatorNameTokens->skelBindingApiAppliedValidator);
TF_AXIOM(validator);

// Create Stage and mesh with a skel binding property
UsdStageRefPtr usdStage = UsdStage::CreateInMemory();
UsdGeomMesh mesh = UsdGeomMesh::Define(usdStage, SdfPath("/SkelRoot/Mesh"));
UsdGeomPrimvarsAPI primvarsApi(mesh);
UsdGeomPrimvar jointIndicesPrimvar = primvarsApi.CreatePrimvar(TfToken("skel:jointIndices"), SdfValueTypeNames->IntArray, UsdGeomTokens->vertex);
jointIndicesPrimvar.Set(VtIntArray{0, 1, 2});

UsdValidationErrorVector errors = validator->Validate(mesh.GetPrim());

// Verify the error for not having the SkelBindingAPI schema applied is present.
TF_AXIOM(errors.size() == 1);
TF_AXIOM(errors[0].GetType() == UsdValidationErrorType::Error);
TF_AXIOM(errors[0].GetSites().size() == 1);
TF_AXIOM(errors[0].GetSites()[0].IsValid());
TF_AXIOM(errors[0].GetSites()[0].IsPrim());
TF_AXIOM(errors[0].GetSites()[0].GetPrim().GetPath() ==
SdfPath("/SkelRoot/Mesh"));
std::string expectedErrorMsg = ("Found a UsdSkelBinding property (primvars:skel:jointIndices)"
", but no SkelBindingAPI applied on the prim </SkelRoot/Mesh>.");
TF_AXIOM(errors[0].GetMessage() == expectedErrorMsg);

// Apply the SkelBindingAPI.
UsdSkelBindingAPI skelBindingApi = UsdSkelBindingAPI::Apply(mesh.GetPrim());

errors = validator->Validate(mesh.GetPrim());

// Verify the error for not having a SkelRoot parenting a prim with the SkelBindingAPI applied.
TF_AXIOM(errors.size() == 1);
TF_AXIOM(errors[0].GetType() == UsdValidationErrorType::Error);
TF_AXIOM(errors[0].GetSites().size() == 1);
TF_AXIOM(errors[0].GetSites()[0].IsValid());
TF_AXIOM(errors[0].GetSites()[0].IsPrim());
TF_AXIOM(errors[0].GetSites()[0].GetPrim().GetPath() ==
SdfPath("/SkelRoot/Mesh"));
expectedErrorMsg = ("UsdSkelBindingAPI applied on prim: </SkelRoot/Mesh>, "
"which is not of type SkelRoot or is not rooted at a prim "
"of type SkelRoot, as required by the UsdSkel schema.");
const std::string message = errors[0].GetMessage();
TF_AXIOM(errors[0].GetMessage() == expectedErrorMsg);

// Add SkelRoot as a parent to the mesh.
UsdSkelRoot skelRoot = UsdSkelRoot::Define(usdStage, SdfPath("/SkelRoot"));
errors = validator->Validate(mesh.GetPrim());

// Verify all errors are gone
TF_AXIOM(errors.size() == 0);
tallytalwar marked this conversation as resolved.
Show resolved Hide resolved
}

int
main()
{
TestUsdSkelValidators();
TestUsdSkelBindingApiAppliedValidator();
printf("OK\n");
return EXIT_SUCCESS;
};
17 changes: 17 additions & 0 deletions pxr/usd/usdSkel/validatorTokens.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Copyright 2024 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//

#include "pxr/usd/usdSkel/validatorTokens.h"

PXR_NAMESPACE_OPEN_SCOPE

TF_DEFINE_PUBLIC_TOKENS(UsdSkelValidatorNameTokens,
USD_SKEL_VALIDATOR_NAME_TOKENS);
TF_DEFINE_PUBLIC_TOKENS(UsdSkelValidatorKeywordTokens,
USD_SKEL_VALIDATOR_KEYWORD_TOKENS);

PXR_NAMESPACE_CLOSE_SCOPE
34 changes: 34 additions & 0 deletions pxr/usd/usdSkel/validatorTokens.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Copyright 2024 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//
#ifndef USDSKEL_VALIDATOR_TOKENS_H
#define USDSKEL_VALIDATOR_TOKENS_H
/// \file
#include "pxr/pxr.h"
#include "pxr/usd/usdSkel/api.h"
#include "pxr/base/tf/staticTokens.h"
PXR_NAMESPACE_OPEN_SCOPE

#define USD_SKEL_VALIDATOR_NAME_TOKENS \
((skelBindingApiAppliedValidator, "usdSkel:SkelBindingApiAppliedValidator"))
#define USD_SKEL_VALIDATOR_KEYWORD_TOKENS \
(UsdSkelValidators)

///\def
/// Tokens representing validator names. Note that for plugin provided
/// validators, the names must be prefixed by usdSkel:, which is the name of
/// the usdSkel plugin.
TF_DECLARE_PUBLIC_TOKENS(UsdSkelValidatorNameTokens, USDSKEL_API,
USD_SKEL_VALIDATOR_NAME_TOKENS);
///\def
/// Tokens representing keywords associated with any validator in the usdShade
/// plugin. Clients can use this to inspect validators contained within a
/// specific keywords, or use these to be added as keywords to any new
/// validator.
TF_DECLARE_PUBLIC_TOKENS(UsdSkelValidatorKeywordTokens, USDSKEL_API,
USD_SKEL_VALIDATOR_KEYWORD_TOKENS);
PXR_NAMESPACE_CLOSE_SCOPE
#endif
89 changes: 89 additions & 0 deletions pxr/usd/usdSkel/validators.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// Copyright 2024 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//
#include "pxr/usd/usd/prim.h"
#include "pxr/usd/usd/schemaRegistry.h"
#include "pxr/usd/usd/validationError.h"
#include "pxr/usd/usd/validationRegistry.h"
#include "pxr/usd/usd/validator.h"
#include "pxr/base/tf/token.h"
#include "pxr/base/tf/stringUtils.h"
#include "pxr/usd/usdSkel/validatorTokens.h"
#include "pxr/usd/usdSkel/bindingAPI.h"

#include <algorithm>
#include <unordered_map>

PXR_NAMESPACE_OPEN_SCOPE

static
UsdValidationErrorVector
_SkelBindingApiAppliedValidator(const UsdPrim &usdPrim)
{
UsdValidationErrorVector errors;

if (!usdPrim.HasAPI<UsdSkelBindingAPI>()){
std::vector<TfToken> primPropertyNames = usdPrim.GetPropertyNames();
Copy link
Contributor

@tallytalwar tallytalwar Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Its a good practice to declare variables just before use. So I would move primPropertyNames below skelPropertyNames.

Also you can make this a const vector.

static const std::unordered_set<TfToken, TfHash> skelPropertyNames = []() {
UsdSchemaRegistry& usdSchemaRegistry = UsdSchemaRegistry::GetInstance();
std::unique_ptr<UsdPrimDefinition> primDef = usdSchemaRegistry.BuildComposedPrimDefinition(TfToken(), {TfToken("SkelBindingAPI")});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh apologies, I should have noticed this before, you can use UsdSkelTokens->SkelBindingAPI, instead of TfToken("SkelBindingAPI"). Such tokens are often generated along with the schema.

https://github.com/PixarAnimationStudios/OpenUSD/blob/release/pxr/usd/usdSkel/tokens.cpp#L55

std::vector<TfToken> vectorOfNames = primDef->GetPropertyNames();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const vector

return std::unordered_set<TfToken, TfHash>(vectorOfNames.begin(), vectorOfNames.end());
}();

for (const TfToken &primToken : primPropertyNames){
if (skelPropertyNames.find(primToken) == skelPropertyNames.end()){
continue;
}
errors.emplace_back(
UsdValidationErrorType::Error,
UsdValidationErrorSites{
UsdValidationErrorSite(usdPrim.GetStage(),
usdPrim.GetPath())
},
TfStringPrintf(("Found a UsdSkelBinding property (%s), "
"but no SkelBindingAPI applied on the prim <%s>."),
primToken.GetText(), usdPrim.GetPath().GetText()));
break;
}
}
else {
if (usdPrim.GetTypeName() != UsdSkelTokens->SkelRoot) {
UsdPrim parentPrim = usdPrim.GetParent();
while (parentPrim && !parentPrim.IsPseudoRoot()) {
if (parentPrim.GetTypeName() != UsdSkelTokens->SkelRoot) {
parentPrim = parentPrim.GetParent();
}
else {
return errors;
}
}
errors.emplace_back(
UsdValidationErrorType::Error,
UsdValidationErrorSites{
UsdValidationErrorSite(usdPrim.GetStage(),
usdPrim.GetPath())
},
TfStringPrintf(("UsdSkelBindingAPI applied on prim: <%s>, "
"which is not of type SkelRoot or is not rooted at a prim "
"of type SkelRoot, as required by the UsdSkel schema."),
usdPrim.GetPath().GetText()));
}
}

return errors;
}

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

registry.RegisterPluginValidator(
UsdSkelValidatorNameTokens->skelBindingApiAppliedValidator,
_SkelBindingApiAppliedValidator);
}

PXR_NAMESPACE_CLOSE_SCOPE