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 and IssueFixer.

  • 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. the Usd.Prim).

  • Fix. If a suggestion is available, it will help to address the Issue found. Information on the Rule 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 their upAxis and metersPerUnit. Stages that can be consumed as referencable assets should furthermore have a valid defaultPrim declared, and stages meant for consumer-level packaging should always have upAxis set to Y.

  • 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 to true. if set to false, no rules will be automatically loaded.

  • enableRule: A method of ValidationEngine 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 with FixResult.

  • 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.