Omni Asset Validator (Tutorial)#
Introduction#
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. For use in Omniverse Kit applications.
The following tutorial will help you to:
Run basic operations for Asset Validator,
ValidationEngineandIssueFixer.Get familiar with existing Rules to diagnose and fix problems.
Create your custom Rules.
Tutorials#
Enabling The Asset Validator Extension#
Caution
As of kit 106.0.1, these extensions are enabled by default within the USD Composer app template in the sample app templates. If you are creating your own application, for example based on the kit base editor app template, the Asset Validator will need to be added as a dependency to the application’s .kit file. More information can be found here: Kit App Template.
The extension can be enabled/disabled via the Window -> Extensions window if applicable, otherwise the dependencies need to be added/removed from your Kit application <my_app_name>.kit file.
[dependencies]
"omni.asset_validator.core" = {}
"omni.asset_validator.ui" = {}
Note
Any changes to .kit files will need the application to be rebuilt for changes to take effect.
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.
To run a simple asset validation, with Script Editor execute the following code.
1import omni.asset_validator.core
2from pxr import Usd, UsdGeom
3
4stage = Usd.Stage.CreateInMemory("tutorial.usda")
5UsdGeom.Xform.Define(stage, "/World")
6
7engine = omni.asset_validator.core.ValidationEngine()
8print(engine.validate(stage))
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=OmniDefaultPrimChecker,
22 at=StageId(root_layer=LayerId(identifier='tutorial.usda')),
23 suggestion=suggestion(callable=update_default_prim, message='updates the default prim', at=none)
24 ),
25 Issue(
26 message="Stage has missing or invalid defaultPrim.",
27 severity=IssueSeverity.FAILURE,
28 rule=StageMetadataChecker,
29 at=None,
30 suggestion=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
Rulethat found the issue and its location (i.e. theUsd.Prim).Fix. If a suggestion is available, it will help to address the
Issuefound. Information on theRuleand 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 theirupAxisandmetersPerUnit. Stages that can be consumed as referencable assets should furthermore have a validdefaultPrimdeclared, and stages meant for consumer-level packaging should always have upAxis set toY.OmniDefaultPrimChecker: Omniverse requires a single, active,Xformableroot 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
3rules: list[str] = []
4for category in omni.asset_validator.core.ValidationRulesRegistry.categories():
5 for rule in omni.asset_validator.core.ValidationRulesRegistry.rules(category=category):
6 rules.append(rule.__name__)
7
8rules.sort()
9for rule in rules:
10 print(rule)
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
2from pxr import Usd, UsdGeom
3
4stage = Usd.Stage.CreateInMemory("tutorial.usda")
5UsdGeom.Xform.Define(stage, "/World")
6
7engine = omni.asset_validator.core.ValidationEngine(init_rules=False)
8engine.enable_rule(omni.asset_validator.core.OmniDefaultPrimChecker)
9print(engine.validate(stage))
There are two new elements present here:
initRules: By default set totrue. if set tofalse, no rules will be automatically loaded.enableRule: A method ofValidationEngineto 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
2from pxr import Usd, UsdGeom
3
4stage = Usd.Stage.CreateInMemory("tutorial.usda")
5UsdGeom.Xform.Define(stage, "/World")
6
7fixer = omni.asset_validator.core.IssueFixer(asset=stage)
8fixer.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
2from pxr import Usd, UsdGeom
3
4# Create a stage
5stage = Usd.Stage.CreateInMemory("tutorial.usda")
6UsdGeom.Xform.Define(stage, "/World")
7
8# Detect issues
9engine = omni.asset_validator.core.ValidationEngine(init_rules=False)
10engine.enable_rule(omni.asset_validator.core.OmniDefaultPrimChecker)
11result = engine.validate(stage)
12
13# Fix issues
14fixer = omni.asset_validator.core.IssueFixer(asset=stage)
15fixer.fix(result.issues())
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, UsdGeom
3
4
5class MyRule(omni.asset_validator.core.BaseRuleChecker):
6 def CheckPrim(self, prim: Usd.Prim) -> None:
7 ...
8
9
10stage = Usd.Stage.CreateInMemory("tutorial.usda")
11UsdGeom.Xform.Define(stage, "/World")
12
13engine = omni.asset_validator.core.ValidationEngine(init_rules=False)
14engine.enable_rule(MyRule)
15print(engine.validate(stage))
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, UsdGeom
3
4
5class MyRule(omni.asset_validator.core.BaseRuleChecker):
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
13
14stage = Usd.Stage.CreateInMemory("tutorial.usda")
15UsdGeom.Xform.Define(stage, "/Hello/World")
16
17engine = omni.asset_validator.core.ValidationEngine(init_rules=False)
18engine.enable_rule(MyRule)
19print(engine.validate(stage))
There are three levels of issues:
Errors: Errors are used to notify user that something unexpected happened that would not let the
Rulerun (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
Issuein Asset Validation. This can be done through_AddFailedCheckas seen in the example above.Infos: Information that is reported by the rules to notify users something is important and not an error nor a potential problem. Also, it doesn’t need to be fixed. It can be added through the method
_AddInfo.
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
atattribute). This will help us to scope down our fix.
1import omni.asset_validator.core
2from pxr import Usd, UsdGeom
3
4
5class MyRule(omni.asset_validator.core.BaseRuleChecker):
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
20stage = Usd.Stage.CreateInMemory("tutorial.usda")
21UsdGeom.Xform.Define(stage, "/Hello/World")
22
23engine = omni.asset_validator.core.ValidationEngine(init_rules=False)
24engine.enable_rule(MyRule)
25print(engine.validate(stage))
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
4
5class MyRule(omni.asset_validator.core.BaseRuleChecker):
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
20
21stage = Usd.Stage.CreateInMemory("tutorial.usda")
22stage.DefinePrim("/Hello/World", "Xform")
23
24engine = omni.asset_validator.core.ValidationEngine(init_rules=False)
25engine.enable_rule(MyRule)
26result = engine.validate(stage)
27
28fixer = omni.asset_validator.core.IssueFixer(asset=stage)
29result = fixer.fix(result.issues())
30print(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
5@omni.asset_validator.core.registerRule("MyCategory")
6class MyRule(omni.asset_validator.core.BaseRuleChecker):
7 def CheckPrim(self, prim: Usd.Prim) -> None:
8 pass
9
10
11for rule in omni.asset_validator.core.ValidationRulesRegistry.rules(category="MyCategory"):
12 print(rule.__name__)
1MyRule
Custom Rule: Locations#
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:
1import omni.asset_validator.core
2from pxr import Usd, UsdGeom
3
4layer_stage = Usd.Stage.CreateInMemory("layer.usda")
5UsdGeom.Sphere.Define(layer_stage, "/Hello/World")
6
7stage = Usd.Stage.CreateInMemory("tutorial.usda")
8stage.GetRootLayer().subLayerPaths.append(layer_stage.GetRootLayer().identifier)
9sphere = UsdGeom.Sphere.Define(stage, "/Hello/World")
10sphere.AddTranslateOp().Set((-250, 0, 0))
11
12# We inspect a specific prim
13prim = stage.GetPrimAtPath("/Hello/World")
14
15
16# We create the data model for the issue
17def Callable(stage: Usd.Stage, location: Usd.Prim) -> None:
18 raise NotImplementedError()
19
20
21issue = omni.asset_validator.core.Issue(
22 message="Goodbye!",
23 at=prim,
24 severity=omni.asset_validator.core.IssueSeverity.FAILURE,
25 suggestion=omni.asset_validator.core.Suggestion(message="Avoids saying goodbye!", callable=Callable),
26)
27
28# Inspect the fixing points for the suggestion
29for fix_at in issue.all_fix_sites:
30 layer_id = fix_at.layer_id
31 path = fix_at.path
32 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='tutorial.usda') /Hello/World
2LayerId(identifier='layer.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.
1import omni.asset_validator.core
2from pxr import Sdf, Usd, UsdGeom
3
4layer_stage = Usd.Stage.CreateInMemory("layer.usda")
5UsdGeom.Sphere.Define(layer_stage, "/Hello/World")
6
7stage = Usd.Stage.CreateInMemory("tutorial.usda")
8stage.GetRootLayer().subLayerPaths.append(layer_stage.GetRootLayer().identifier)
9sphere = UsdGeom.Sphere.Define(stage, "/Hello/World")
10sphere.AddTranslateOp().Set((-250, 0, 0))
11
12prim = stage.GetPrimAtPath("/Hello/World")
13
14# We create the data model for the issue
15def Callable(stage: Usd.Stage, location: Usd.Prim) -> None:
16 raise NotImplementedError()
17
18
19issue = omni.asset_validator.core.Issue(
20 message="Goodbye!",
21 at=prim,
22 severity=omni.asset_validator.core.IssueSeverity.FAILURE,
23 suggestion=omni.asset_validator.core.Suggestion(
24 message="Avoids saying goodbye!",
25 callable=Callable,
26 at=[Sdf.Layer.FindOrOpen(layer_stage.GetRootLayer().identifier)],
27 ),
28)
29
30# Inspect the fixing points for the suggestion
31for fix_at in issue.all_fix_sites:
32 layer_id = fix_at.layer_id
33 path = fix_at.path
34 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='layer.usda') /Hello/World
2LayerId(identifier='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
FixResultand 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
ValidationEngineagain, 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.