Authoring rules#
usd-validation-nvidia is rule-based: each check is a class that inspects a
Usd.Stage (or its layers, prims, or package contents) and reports
Issue objects. This page shows how to write, register,
and distribute your own rules.
The rule interface#
A rule subclasses BaseRuleChecker and overrides one or
more Check* callbacks. The engine invokes them as it walks an asset, so a rule only
implements the callbacks it needs:
Callback |
Invoked with |
|---|---|
|
The composed |
|
Each |
|
Each |
|
The stage together with its layer and asset dependencies. |
|
Asset paths that did not resolve. |
|
Diagnostics emitted while opening/composing the stage. |
|
Each |
|
Each format-specific dependency (a |
|
Hook to clear any per-asset state the rule caches between runs. |
Report findings from inside a callback with the helper methods, in increasing severity:
self._AddInfo(message, ...)- informational only.self._AddWarning(message, ...)- a non-blocking concern.self._AddFailedCheck(message, ...)- a normative failure.self._AddError(message, ...)- the rule itself could not run.
Each accepts an at= location (a prim, property, layer, or other
Identifier), an optional code, and an optional
requirement. _AddFailedCheck, _AddError, and _AddWarning also take one or
more suggestions used for auto-fixing (see below); _AddInfo does not.
Registering a rule#
Decorate the class with register_rule() and a category
name. Registered rules become part of every default ValidationEngine:
from pxr import UsdGeom
from usd_validation_nvidia import BaseRuleChecker, register_rule
@register_rule("MyStudio")
class VisibilityAttributeChecker(BaseRuleChecker):
"""Meshes should author a 'visibility' attribute."""
def CheckPrim(self, prim):
if not prim.IsA(UsdGeom.Mesh):
return
if not prim.GetAttribute("visibility").IsValid():
self._AddWarning(
message=f"Mesh <{prim.GetPath()}> does not author 'visibility'.",
at=prim,
)
Run it like any other rule; an engine created with no arguments discovers all registered rules:
from usd_validation_nvidia import ValidationEngine
engine = ValidationEngine()
results = engine.validate("asset.usda")
register_rule also accepts skip= (register conditionally, for example in tests) and
overwrite= (replace an existing rule in a category). Categories group related rules:
the CLI’s --category flag and register_rule use them. To turn individual rules on or
off on an engine, use enable_rule() / disable_rule().
Providing an auto-fix#
Attach a Suggestion to an issue to make it fixable.
IssueFixer applies the suggestion to the stage and
writes the result back:
from usd_validation_nvidia import IssueFixer
fixer = IssueFixer("asset.usda")
fixer.fix(results.issues())
fixer.save()
See Python API for the Suggestion, IssueFixer, and Issue signatures.
Wrapping a native USD validator#
OpenUSD ships its own UsdValidation validators. Rather than reimplement one, subclass
UsdValidatorAdapter and name the validator to expose it
as a rule:
from usd_validation_nvidia import register_rule, UsdValidatorAdapter
@register_rule("Basic", skip="usdUtilsValidators:UsdzPackageValidator" not in UsdValidatorAdapter)
class UsdzPackageValidator(UsdValidatorAdapter):
@classmethod
def validator_name(cls):
return "usdUtilsValidators:UsdzPackageValidator"
Running a callback in a process pool#
By default every rule callback runs in the calling process. Decorate a callback with
multiprocess_safe() to declare it eligible for the
process pool that --process COUNT enables:
from usd_validation_nvidia import BaseRuleChecker, multiprocess_safe, register_rule
@register_rule("Basic")
class MyLayerRule(BaseRuleChecker):
@multiprocess_safe
def CheckLayer(self, layer):
...
The decorator is a declaration, not a request: marked callbacks run in the pool only when process execution is enabled, and behave exactly as before when it is not. This feature is experimental.
A callback is only safe to mark if it holds to both of these:
Its arguments and results survive pickling. Work crosses a process boundary, so anything the callback receives or reports must be picklable. Validation identifiers and layer compliance events already are.
It shares no mutable state with the parent process. Accumulating results on
selfacross calls does not work — a worker mutates its own copy, and the parent never sees it. Report findings through the usual issue helpers instead.
CheckStage and CheckPrim callbacks that traverse a live stage are generally not
safe to mark, because the stage does not cross the process boundary.
Distributing rules as a plugin#
Rules register when their module is imported. To ship rules in your own package so they
load automatically, expose a plugin through the usd_validation_nvidia entry-point group;
the plugin manager discovers it at startup:
# pyproject.toml of your rule package
[project.entry-points."usd_validation_nvidia"]
my_studio_rules = "my_studio_rules:Plugin"
The referenced object implements PluginProtocol: an
on_startup() hook, where you import or register your rule modules, and an
on_shutdown() hook. DefaultPlugin is the built-in
reference implementation. The legacy omni.asset_validator group is also scanned for
backward compatibility, but new plugins should use usd_validation_nvidia.
Loading authored specs and loose rules#
For applications that load authored profile/specification content at runtime, use
usd_profiles_nvidia.SpecificationsLoader to parse the content and
register_content() to register its requirement
definitions, capabilities, features, and profiles. Loose Python validator modules have a
separate lifecycle token from register_validation_modules():
from usd_profiles_nvidia import SpecificationsLoader
from usd_validation_nvidia import (
register_content,
register_validation_modules,
unregister_content,
unregister_validation_modules,
)
content = SpecificationsLoader(
root_dir="specs",
reverse_domain="com.example.assets",
).load()
registered_modules = None
try:
register_content(content)
registered_modules = register_validation_modules(["specs/capabilities/geometry/validation.py"])
...
finally:
if registered_modules is not None:
unregister_validation_modules(registered_modules)
unregister_content(content)
The loaded content object is the lifecycle token for documentation/specification
content. The registered_modules token owns imported rule modules, so reload and
shutdown code can unregister exactly what it previously loaded.