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:
1import omni.asset_validator.core
2from pxr import Usd
3
4stage = Usd.Stage.Open(omni.asset_validator.core.BASIC_TUTORIAL_PATH)
5print(stage.ExportToString())
The contents should be equivalent to:
1#usda 1.0
2(
3 doc = """Generated from Composed Stage of root layer tutorial.usda
4"""
5)
6
7def Xform "Hello"
8{
9 def Sphere "World"
10 {
11 }
12}
13
To run a simple asset validation, with Script Editor
execute the following code.
1import omni.asset_validator.core
2
3engine = omni.asset_validator.core.ValidationEngine()
4print(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.
1Results(
2 asset="tutorial.usda",
3 issues=[
4 Issue(
5 message="Stage does not specify an upAxis.",
6 severity=IssueSeverity.FAILURE,
7 rule=StageMetadataChecker,
8 at=None,
9 suggestion=None
10 ),
11 Issue(
12 message="Stage does not specify its linear scale in metersPerUnit.",
13 severity=IssueSeverity.FAILURE,
14 rule=StageMetadataChecker,
15 at=None,
16 suggestion=None
17 ),
18 Issue(
19 message="Stage has missing or invalid defaultPrim.",
20 severity=IssueSeverity.FAILURE,
21 rule=StageMetadataChecker,
22 at=None,
23 suggestion=None
24 ),
25 Issue(
26 message="Stage has missing or invalid defaultPrim.",
27 severity=IssueSeverity.FAILURE,
28 rule=OmniDefaultPrimChecker,
29 at=StageId(root_layer=LayerId(identifier='tutorial.usda')),
30 suggestion=suggestion(callable=update_default_prim, message='updates the default prim', at=none)
31 )
32 ]
33)
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
.
1import omni.asset_validator.core
2
3for category in omni.asset_validator.core.ValidationRulesRegistry.categories():
4 for rule in omni.asset_validator.core.ValidationRulesRegistry.rules(category=category):
5 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:
1import omni.asset_validator.core
2
3engine = omni.asset_validator.core.ValidationEngine(initRules=False)
4engine.enableRule(omni.asset_validator.core.OmniDefaultPrimChecker)
5print(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:
1Results(
2 asset="tutorial.usda",
3 issues=[
4 Issue(
5 message="Stage has missing or invalid defaultPrim.",
6 severity=IssueSeverity.FAILURE,
7 rule=OmniDefaultPrimChecker,
8 at=StageId(root_layer=LayerId(identifier='tutorial.usda')),
9 suggestion=suggestion(callable=update_default_prim, message='updates the default prim', at=none)
10 )
11 ]
12)
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.
1import omni.asset_validator.core
2
3fixer = omni.asset_validator.core.IssueFixer(asset=omni.asset_validator.core.BASIC_TUTORIAL_PATH)
4fixer.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.
1import omni.asset_validator.core
2
3# Detect issues
4engine = omni.asset_validator.core.ValidationEngine(initRules=False)
5engine.enableRule(omni.asset_validator.core.OmniDefaultPrimChecker)
6result = engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH)
7
8# Fix issues
9fixer = omni.asset_validator.core.IssueFixer(asset=omni.asset_validator.core.BASIC_TUTORIAL_PATH)
10fixer.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
:
1import omni.asset_validator.core
2from pxr import Usd
3
4stage = Usd.Stage.Open(omni.asset_validator.core.BASIC_TUTORIAL_PATH)
5print(stage.ExportToString())
We can find the issue reported by OmniDefaultPrimChecker
is fixed:
1#usda 1.0
2(
3 defaultPrim="Hello"
4 doc = """Generated from Composed Stage of root layer tutorial.usda
5"""
6)
7
8def Xform "Hello"
9{
10 def Sphere "World"
11 {
12 }
13}
14
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:
1import omni.asset_validator.core
2from pxr import Usd
3
4class MyRule(omni.asset_validator.core.BaseRuleChecker):
5
6 def CheckPrim(self, prim: Usd.Prim) -> None:
7 pass
8
9engine = omni.asset_validator.core.ValidationEngine(initRules=False)
10engine.enableRule(MyRule)
11print(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.
1Results(
2 asset="tutorial.usda",
3 issues=[
4
5 ]
6)
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
:
1import omni.asset_validator.core
2from pxr import Usd
3
4class MyRule(omni.asset_validator.core.BaseRuleChecker):
5
6 def CheckPrim(self, prim: Usd.Prim) -> None:
7 if prim.GetPath() == "/Hello/World":
8 self._AddFailedCheck(
9 message="Goodbye!",
10 at=prim,
11 )
12
13engine = omni.asset_validator.core.ValidationEngine(initRules=False)
14engine.enableRule(MyRule)
15print(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:
1Results(
2 asset="tutorial.usda",
3 issues=[
4 Issue(
5 message="Goodbye!",
6 severity=IssueSeverity.FAILURE,
7 rule=MyRule,
8 at=PrimId(stage_id=StageId(root_layer=LayerId(identifier='tutorial.usda')), path=/Hello/World),
9 suggestion=None
10 )
11 ]
12)
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.
1import omni.asset_validator.core
2from pxr import Usd
3
4class MyRule(omni.asset_validator.core.BaseRuleChecker):
5
6 def Callable(self, stage: Usd.Stage, location: Usd.Prim) -> None:
7 raise NotImplementedError()
8
9 def CheckPrim(self, prim: Usd.Prim) -> None:
10 if prim.GetPath() == "/Hello/World":
11 self._AddFailedCheck(
12 message="Goodbye!",
13 at=prim,
14 suggestion=omni.asset_validator.core.Suggestion(
15 message="Avoids saying goodbye!",
16 callable=self.Callable,
17 )
18 )
19
20engine = omni.asset_validator.core.ValidationEngine(initRules=False)
21engine.enableRule(MyRule)
22print(engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH))
It will now produce the following output.
1Results(
2 asset="tutorial.usda",
3 issues=[
4 Issue(
5 message="Goodbye!",
6 severity=IssueSeverity.FAILURE,
7 rule=MyRule,
8 at=PrimId(stage_id=StageId(root_layer=LayerId(identifier='tutorial.usda')), path=/Hello/World),
9 suggestion=Suggestion(callable=Callable, message='Avoids saying goodbye!', at=None)
10 )
11 ]
12)
As we can see we have now a suggestion with a description and a method to invoke. The full example will be:
1import omni.asset_validator.core
2from pxr import Usd
3
4class MyRule(omni.asset_validator.core.BaseRuleChecker):
5
6 def Callable(self, stage: Usd.Stage, location: Usd.Prim) -> None:
7 raise NotImplementedError()
8
9 def CheckPrim(self, prim: Usd.Prim) -> None:
10 if prim.GetPath() == "/Hello/World":
11 self._AddFailedCheck(
12 message="Goodbye!",
13 at=prim,
14 suggestion=omni.asset_validator.core.Suggestion(
15 message="Avoids saying goodbye!",
16 callable=self.Callable,
17 )
18 )
19
20engine = omni.asset_validator.core.ValidationEngine(initRules=False)
21engine.enableRule(MyRule)
22result = engine.validate(omni.asset_validator.core.BASIC_TUTORIAL_PATH)
23
24fixer = omni.asset_validator.core.IssueFixer(asset=omni.asset_validator.core.BASIC_TUTORIAL_PATH)
25result = fixer.fix(result.issues())
26print(result)
Notice how the NotImplementedError
error was not thrown during fixer.fix
. However, we can access the result of
execution by inspecting result
:
1FixResultList(
2 results=[
3 FixResult(
4 Issue(
5 message="Goodbye!",
6 severity=IssueSeverity.FAILURE,
7 rule=MyRule,
8 at=PrimId(stage_id=StageId(root_layer=LayerId(identifier='tutorial.usda')), path=/Hello/World),
9 suggestion=Suggestion(callable=Callable, message='Avoids saying goodbye!', at=None)
10 ),
11 status=FixStatus.FAILURE,
12 exception=NotImplementedError
13 )
14 ]
15)
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
.
1import omni.asset_validator.core
2from pxr import Usd
3
4@omni.asset_validator.core.registerRule("MyCategory")
5class MyRule(omni.asset_validator.core.BaseRuleChecker):
6
7 def CheckPrim(self, prim: Usd.Prim) -> None:
8 pass
9
10for rule in omni.asset_validator.core.ValidationRulesRegistry.rules(category="MyCategory"):
11 print(rule.__name__)
1MyRule
Custom Rule: Locations
For this section, let us use LAYERS_TUTORIAL_PATH. We proceed like in the previous section:
1import omni.asset_validator.core
2from pxr import Usd
3
4stage = Usd.Stage.Open(omni.asset_validator.core.LAYERS_TUTORIAL_PATH)
5print(stage.ExportToString())
The contents should be equivalent to:
1#usda 1.0
2(
3 doc = """Generated from Composed Stage of root layer layers_tutorial.usda
4"""
5)
6
7def Xform "Hello"
8{
9 def Sphere "World"
10 {
11 double3 xformOp:translate = (-250, 0, 0)
12 uniform token[] xformOpOrder = ["xformOp:translate"]
13 }
14}
15
What we are doing is adding an opinion to the prim “/Hello/World”. In the previous section we learned how to create a Rule
and issue a Failure
.
The data model is similar to the following code snippet:
1from pxr import Usd
2import omni.asset_validator.core
3
4# We open the stage
5stage = Usd.Stage.Open(omni.asset_validator.core.LAYERS_TUTORIAL_PATH)
6
7# We inspect a specific prim
8prim = stage.GetPrimAtPath("/Hello/World")
9
10# We create the data model for the issue
11def Callable(stage: Usd.Stage, location: Usd.Prim) -> None:
12 raise NotImplementedError()
13
14issue = omni.asset_validator.core.Issue(
15 message="Goodbye!",
16 at=prim,
17 severity= omni.asset_validator.core.IssueSeverity.FAILURE,
18 suggestion=omni.asset_validator.core.Suggestion(
19 message="Avoids saying goodbye!",
20 callable=Callable
21 ),
22)
23
24# Inspect the fixing points for the suggestion
25for fix_at in issue.all_fix_sites:
26 layer_id = fix_at.layer_id
27 path = fix_at.path
28 print(layer_id, path)
The output of the above snippet should show first the path of layers tutorial (i.e. LAYERS_TUTORIAL_PATH
) and second the basic tutorial (i.e. BASIC_TUTORIAL_PATH
).
While this may be correct for above issue, different issues may need to override this information.
1LayerId(identifier='layers_tutorial.usda') /Hello/World
2LayerId(identifier='tutorial.usda') /Hello/World
Every issue, has associated fixing sites (i.e. property all_fix_sites
). The fixing sites are all places that contribute opinions to the prim from strongest
to
weakest
order. When no layer is provided to fix, by default will be the strongest
. If no indicated (as above) the preferred site will be the Root
layer.
To change the preferred site to fix, we can add the at
attribute to Suggestion
.
1from pxr import Usd, Sdf
2import omni.asset_validator.core
3
4# We open the stage
5stage = Usd.Stage.Open(omni.asset_validator.core.LAYERS_TUTORIAL_PATH)
6
7# We inspect a specific prim
8prim = stage.GetPrimAtPath("/Hello/World")
9
10# We create the data model for the issue
11def Callable(stage: Usd.Stage, location: Usd.Prim) -> None:
12 raise NotImplementedError()
13
14issue = omni.asset_validator.core.Issue(
15 message="Goodbye!",
16 at=prim,
17 severity= omni.asset_validator.core.IssueSeverity.FAILURE,
18 suggestion=omni.asset_validator.core.Suggestion(
19 message="Avoids saying goodbye!",
20 callable=Callable,
21 at=[Sdf.Layer.FindOrOpen(omni.asset_validator.core.BASIC_TUTORIAL_PATH)]
22 ),
23)
24
25# Inspect the fixing points for the suggestion
26for fix_at in issue.all_fix_sites:
27 layer_id = fix_at.layer_id
28 path = fix_at.path
29 print(layer_id, path)
The output will change the order now, and you should see basic tutorial path first (i.e. BASIC_TUTORIAL_PATH
).
1LayerId(identifier='tutorial.usda') /Hello/World
2LayerId(identifier='layers_tutorial.usda') /Hello/World
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.