Omni Asset Validator (Tutorial)
Introduction
Omniverse Asset Validator
is an extensible framework to validate USD.
Initially inspired from Pixar Compliance Checker,
extends upon these ideas and adds more validation rules applicable throughout Omniverse, as well as adding the ability
to automatically fix issues.
Currently, there are two main components:
Omni Asset Validator (Core): Core components for Validation engine. Feel free to use this component to implement programmatic functionality or extend Core functionality through its framework.
Omni Asset Validator (UI): Convenient UI for Omniverse. Used for daily tasks with Omniverse tools as Create.
The following tutorial will help you to:
Run basic operations for Asset Validator,
ValidationEngine
andIssueFixer
.Get familiar with existing Rules to diagnose and fix problems.
Create your custom Rules.
Tutorials
Testing assets
In order to run Asset Validator, we need to enable the extension Omni asset validator (Core)
. Optionally we can also
enable Omni asset validator (UI)
to perform similar operations using the user interface. Through this tutorial we will
use Script Editor
, enable it under the Window
menu.
The following tutorial uses the file BASIC_TUTORIAL_PATH
which is bundled together with omni.asset_validator.core
.
We can see its contents with the following snippet:
import omni.asset_validator.core
from pxr import Usd
stage = Usd.Stage.Open(omni.asset_validator.core.BASIC_TUTORIAL_PATH)
print(stage.ExportToString())
The contents should be equivalent to:
#usda 1.0
(
doc="""Generated from Composed Stage of root layer C:\\some\\location\exts\\omni.asset_validator.core\\omni\\asset_validator\\core\\resources\\tutorial.usda"""
)
def Xform "Hello"
{
def Sphere "World"
{
}
}
To run a simple asset validation, with Script Editor
execute the following code.
import omni.asset_validator.core
engine = omni.asset_validator.core.ValidationEngine()
print(engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH))
Note
For more information about ValidationEngine together with more examples can be found at the ValidationEngine API.
The above code would produce similar results to.
Results(
asset="C:\some\location\exts\omni.asset_validator.core\omni\asset_validator\core\resources\tutorial.usda",
issues=[
Issue(
message="Stage does not specify an upAxis.",
severity=IssueSeverity.FAILURE,
rule=StageMetadataChecker,
at=None,
suggestion=None
),
Issue(
message="Stage does not specify its linear scale in metersPerUnit.",
severity=IssueSeverity.FAILURE,
rule=StageMetadataChecker,
at=None,
suggestion=None
),
Issue(
message="Stage has missing or invalid defaultPrim.",
severity=IssueSeverity.FAILURE,
rule=StageMetadataChecker,
at=None,
suggestion=None
),
Issue(
message="Stage has missing or invalid defaultPrim.",
severity=IssueSeverity.FAILURE,
rule=OmniDefaultPrimChecker,
at=StageId(
identifier="C:\some\location\exts\omni.asset_validator.core\omni\asset_validator\core\resources\tutorial.usda"
),
suggestion=Suggestion(
callable=UpdateDefaultPrim,
message="Updates the default prim"
)
)
]
)
The main result of validation engine is called an Issue
. The main task of Asset validation is to detect and fix issues.
An Issue
has important information on how to achieve both tasks.
Detect. Once an issue has been found it offers a description of the problem (through a human-readable message), its severity, the
Rule
that found the issue and its location (i.e. theUsd.Prim
).Fix. If a suggestion is available, it will help to address the
Issue
found. Information on theRule
and the location of the issue will be used to address it.
In the following section we will walk you through on how to identify and fix issues.
Note
For more information see the Issue API.
Understanding Rules
Omni asset validator (Core)
ships with multiple rules, in the previous example we already covered two:
StageMetadataChecker
: All stages should declare theirupAxis
andmetersPerUnit
. Stages that can be consumed as referencable assets should furthermore have a validdefaultPrim
declared, and stages meant for consumer-level packaging should always have upAxis set toY
.OmniDefaultPrimChecker
: Omniverse requires a single, active,Xformable
root prim, also set to the layer’s defaultPrim.
Refer to Rules for the rest of rules. In the previous example when calling ValidationEngine
we invoked
all rules available. ValidationRulesRegistry
has a registry of all rules to be used by ValidationEngine
.
import omni.asset_validator.core
for category in omni.asset_validator.core.ValidationRulesRegistry.categories():
for rule in omni.asset_validator.core.ValidationRulesRegistry.rules(category=category):
print(rule.__name__)
If we want to have finer control of what we can execute, we can also specify which rules to run, for example:
import omni.asset_validator.core
engine = omni.asset_validator.core.ValidationEngine(initRules=False)
engine.enableRule(omni.asset_validator.core.OmniDefaultPrimChecker)
print(engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH))
There are two new elements present here:
initRules
: By default set totrue
. if set tofalse
, no rules will be automatically loaded.enableRule
: A method ofValidationEngine
to add rules.
The above would produce the following result:
Results(
asset="C:\some\location\exts\omni.asset_validator.core\omni\asset_validator\core\resources\tutorial.usda",
issues=[
Issue(
message="Stage has missing or invalid defaultPrim.",
severity=IssueSeverity.FAILURE,
rule=OmniDefaultPrimChecker,
at=StageId(
identifier="C:\some\location\exts\omni.asset_validator.core\omni\asset_validator\core\resources\tutorial.usda"
),
suggestion=Suggestion(
callable=UpdateDefaultPrim,
message="Updates the default prim"
)
)
]
)
In this particular case, OmniDefaultPrimChecker
has implemented a suggestion for this specific issue. The second important class in Core
we want to cover is IssueFixer
, the way to invoke it is quite straightforward.
import omni.asset_validator.core
fixer = omni.asset_validator.core.IssueFixer(asset=omni.asset_validator.core.BASIC_TUTORIAL_PATH)
fixer.fix([])
fixer.fix
will receive the list of issues that should be addressed. The list of issues to address can be accessed through issues
method in Results
class.
Note
For more information about IssueFixer see IssueFixer API.
By combining the previous two examples we can now, detect and fix issues for a specific rule.
import omni.asset_validator.core
# Detect issues
engine = omni.asset_validator.core.ValidationEngine(initRules=False)
engine.enableRule(omni.asset_validator.core.OmniDefaultPrimChecker)
result = engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH)
# Fix issues
fixer = omni.asset_validator.core.IssueFixer(asset=omni.asset_validator.core.BASIC_TUTORIAL_PATH)
fixer.fix(result.issues())
Warning
In this tutorial we do temporary changes. To persist your changes uses fixer.save().
If we inspect the file BASIC_TUTORIAL_PATH
:
import omni.asset_validator.core
from pxr import Usd
stage = Usd.Stage.Open(omni.asset_validator.core.BASIC_TUTORIAL_PATH)
print(stage.ExportToString())
We can find the issue reported by OmniDefaultPrimChecker
is fixed:
#usda 1.0
(
defaultPrim="Hello"
doc="""Generated from Composed Stage of root layer C:\\some\\location\exts\\omni.asset_validator.core\\omni\\asset_validator\\core\\resources\\tutorial.usda"""
)
def Xform "Hello"
{
def Sphere "World"
{
}
}
Note
Try to repeat the same steps using the UI.
Custom Rule: Detection
If the shipped rules are not enough for your needs you can also implement your own rule.
ValidationEngine
allows to be extensible, by levering users to add its own rules.
To add a new rule extend from BaseRuleChecker
. The most simple code to achieve this is as follows:
import omni.asset_validator.core
from pxr import Usd
class MyRule(omni.asset_validator.core.BaseRuleChecker):
def CheckPrim(self, prim: Usd.Prim) -> None:
pass
engine = omni.asset_validator.core.ValidationEngine(initRules=False)
engine.enableRule(MyRule)
print(engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH))
We can see our method CheckPrim
being invoked for every Prim. However, our output is empty because CheckPrim
has not notified of any issues.
Results(
asset="C:\some\location\exts\omni.asset_validator.core\omni\asset_validator\core\resources\tutorial.usda",
issues=[
]
)
Note
CheckPrim is not the only method available, see more at BaseRuleChecker API.
To add a bit of logic we can change our class to report a single failure when we encounter the prim whose path is /Hello/World
:
import omni.asset_validator.core
from pxr import Usd
class MyRule(omni.asset_validator.core.BaseRuleChecker):
def CheckPrim(self, prim: Usd.Prim) -> None:
if prim.GetPath() == "/Hello/World":
self._AddFailedCheck(
message="Goodbye!",
at=prim,
)
engine = omni.asset_validator.core.ValidationEngine(initRules=False)
engine.enableRule(MyRule)
print(engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH))
There are three levels of issues:
Errors: Errors are used to notify user that something unexpected happened that would not let the
Rule
run (i.e. File not found error). Added through the method_AddError
.Warnings: Warnings are used to notify users that though correct data is found it could cause a potential problem. Added through the method
_AddWarning
.Failures: The most common way to report an
Issue
in Asset Validation. This can be done through_AddFailedCheck
as seen in the example above.
Above code will generate:
Results(
asset="C:\some\location\exts\omni.asset_validator.core\omni\asset_validator\core\resources\tutorial.usda",
issues=[
Issue(
message="Goodbye!",
severity=IssueSeverity.FAILURE,
rule=MyRule,
at=PrimId(
stage_ref=StageId(
identifier="C:\some\location\exts\omni.asset_validator.core\omni\asset_validator\core\resources\tutorial.usda"
),
path="/Hello/World"
),
suggestion=None
)
]
)
With our Rule
implemented the next step is to propose a suggestion to fix it.
Custom Rule: Fix
The fixing interface requires to implement a Suggestion
. A Suggestion
will take as parameters:
Stage: The original stage where the issue was found.
Location: The location defined in the Issue (i.e. the
at
attribute). This will help us to scope down our fix.
import omni.asset_validator.core
from pxr import Usd
class MyRule(omni.asset_validator.core.BaseRuleChecker):
def Callable(self, stage: Usd.Stage, location: Usd.Prim) -> None:
raise NotImplementedError()
def CheckPrim(self, prim: Usd.Prim) -> None:
if prim.GetPath() == "/Hello/World":
self._AddFailedCheck(
message="Goodbye!",
at=prim,
suggestion=omni.asset_validator.core.Suggestion(
message="Avoids saying goodbye!",
callable=self.Callable,
)
)
engine = omni.asset_validator.core.ValidationEngine(initRules=False)
engine.enableRule(MyRule)
print(engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH))
It will now produce the following output.
Results(
asset="C:\some\location\exts\omni.asset_validator.core\omni\asset_validator\core\resources\tutorial.usda",
issues=[
Issue(
message="Goodbye!",
severity=IssueSeverity.FAILURE,
rule=MyRule,
at=PrimId(
stage_ref=StageId(
identifier="C:\some\location\exts\omni.asset_validator.core\omni\asset_validator\core\resources\tutorial.usda"
),
path="/Hello/World"
),
suggestion=Suggestion(
callable=Callable,
message="Avoids saying goodbye!"
)
)
]
)
As we can see we have now a suggestion with a description and a method to invoke. The full example will be:
import omni.asset_validator.core
from pxr import Usd
class MyRule(omni.asset_validator.core.BaseRuleChecker):
def Callable(self, stage: Usd.Stage, location: Usd.Prim) -> None:
raise NotImplementedError()
def CheckPrim(self, prim: Usd.Prim) -> None:
if prim.GetPath() == "/Hello/World":
self._AddFailedCheck(
message="Goodbye!",
at=prim,
suggestion=omni.asset_validator.core.Suggestion(
message="Avoids saying goodbye!",
callable=self.Callable,
)
)
engine = omni.asset_validator.core.ValidationEngine(initRules=False)
engine.enableRule(MyRule)
result = engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH)
fixer = omni.asset_validator.core.IssueFixer(asset=omni.asset_validator.core.BASIC_TUTORIAL_PATH)
result = fixer.fix(result.issues())
print(result)
Notice how the NotImplementedError
error was not thrown during fixer.fix
. However, we can access the result of
execution by inspecting result
:
[
FixResult(
issue=Issue(
message='Goodbye!',
severity=FAILURE,
rule=<class '__main__.MyRule'>,
at=PrimId(
stage_ref=StageId(identifier='C:\\sources\\asset-validator\\_build\\windows-x86_64\\release\\exts\\omni.asset_validator.core\\omni\\asset_validator\\core\\resources\\tutorial.usda'),
path='/Hello/World'
),
suggestion=Suggestion(callable=Callable, message='Avoids saying goodbye!')
),
status=FAILURE,
exception=NotImplementedError()
)
]
Finally, if you decide to run your custom Rule
with the rest of the rules, it may be useful to register it in
ValidationRulesRegistry
, this can be done using registerRule
.
import omni.asset_validator.core
from pxr import Usd
@omni.asset_validator.core.registerRule("MyCategory")
class MyRule(omni.asset_validator.core.BaseRuleChecker):
def CheckPrim(self, prim: Usd.Prim) -> None:
pass
for rule in omni.asset_validator.core.ValidationRulesRegistry.rules(category="MyCategory"):
print(rule.__name__)
The previous tutorial should have helped you to:
Create a custom Rule, generating an error and a suggestion to fix it.
Run ValidationEngine with a specific rule.
Run IssueFixer to fix specific issues and review the response.
Frequently Asked Questions
Are there any guards to make sure fixes are still relevant / don’t collide?
In our general practice we have noticed:
Fixing an issue may solve another issue. If a consecutive suggestion may fail to be applied, we just keep the exception in
FixResult
and continue execution. You will then decide the steps to take withFixResult
.Fixing an issue may generate another issue. For the second case it is recommended to run
ValidationEngine
again, to discover those cases. Think of it as an iterative process with help of an automated tool.
Are fixes addressed in the root layer? strongest layer?
Currently, some Issues would perform the suggestion on the strongest layer, while many on the root layer. We are working into offer flexibility to decide in which layer aim the changes, while also offering a default layer for automated workflows.