Using The Scene Optimizer#
Finding The Extension#
Note
This extension is enabled by default in USD Composer/Explorer applications 2022.3.0 and above.
Caution
As of Kit 106.0.1 release, this extension is a part of the sample app templates. If you are using the kit_base_editor, the scene optimizer extension dependencies will need to be added in the kit_base_editor.kit
file. More information can be found in Kit App Template.
The Scene Optimizer panel can be opened via the Window -> Utilities -> Scene Optimizer menu item.
The extension can be enabled/disabled via the Window -> Extensions window if applicable, otherwise they need to be added/removed from your Kit application <my_app_name>.kit
file.
Search for Scene Optimizer.
There are three extensions available. You will see:
omni.scene.optimizer.core
omni.scene.optimizer.ui
omni.scene.optimizer.bundle
In most cases enabling the bundle
extension is all that is needed. This will
load both core
and ui
extensions automatically.
Scene Optimizer UI#
When the Scene Optimizer is enabled, the following UI is displayed:
- Load Preset
Load a previously saved JSON configuration file describing a series of processes or choose an included preset:
Modify Instances - Clean Prototypes
Modify Instances - Merge Meshes
Modify Stage - Deduplicate Materials and Meshes
Modify Stage - Move Materials to /Looks
Modify Stage - Merge Meshes
Modify Stage - Spatial Merge Meshes
- Save Preset
Save the current process stack to a JSON file for later use.
- Execute All
Executes all of the processes that are configured from top to bottom.
- Clear All Processes
Removes all processes from the UI.
- Add Scene Optimizer Process
Add processes that will be performed on the stage.
Reordering Processes#
As processes are added they will be appended to the list. Processes can be reordered (click and drag the drag handle
in the top-left corner of a process) or removed (click the delete
button in the top-right corner of a process). You
can also expand/collapse a process by clicking on the top header section of it.
Editing Prim Paths#
Some processes can have a list of prim paths or regular expressions specified in order to restrict what they operate on:
Those processes will have an argument that looks like the screenshot above. You can freely type a prim path
or
regular expression
in the text field. Clicking Add
will add any selected prims in the stage to the list.
Clicking the pencil
icon will open a new window, shown below, that lets you more easily manipulate what is
included.
Presets#
A few presets are included in Load Preset
button to help load common operation stacks and their configurations to help organize common output USD data structures to be more performant for user workflows.
There are two primary categories of presets within Scene Optimizer:
- Instance Optimization
These presets aim to find the prototypes of the instances in the stage and optimize each one. The presets can be executed in succession to achieve higher levels of optimization, mainly by reducing prim count, this is equally beneficial for performance and useability (i.e., less clutter in the Stage Window).
Name
Description
Workflow / Applications
Modify Instances - Clean Prototypes
This preset does not change the prim hierarchy. It performs the following operations:
Enables Instancing on internal references.
Prune Xforms - Removes Xforms which do not have any child.
Compute Extents - Ensures that mesh prims have valid extent attribute values.
Generate projection UVs - If UVs are not found on a mesh, they will be created via Box projection.
Optimize Time Samples - Removed redundant animation time samples.
Note: This preset finds the prototypes hierarchies by matching the path name . This should apply correctly to most content which was converted from other formats but may need to be adjusted for specific cases.
Components in Process and Equipment data.
Instances (families) in architecture data.
Products with important hierarchies that need to be preserved up to the instance level.
Simulation / Process Data with many timesamples.
CAD data with low quality materials.
Modify Instances - Merge Meshes
This preset merges the meshes within each component (in the prototype library) and removes leaf level xforms.
Stages with many meshes in each prototype.
Stages with important hierarchies that need to be preserved up to the instance level.
- Stage Optimization
These presets remove instancing and prototype hierarchy structures and will aim to optimize the entire stage.
Name
Description
Workflow / Applications
Modify Stage - Deduplicate Materials and Meshes
Detects duplicate meshes and turns them into instances. This should result in faster stage load times, faster translation to the renderer and lower memory requirements.
Note: This preset introduces reference arcs across meshes in different prototypes to de-duplicate them. This may make the scene more complex and a bit harder to work with.
Stages with many duplicate meshes across the hierarchy, which are not already instanced.
Modify Stage - Move Materials to /Looks
Prototypes may often contain duplicate materials in each prototype. This can make it difficult to modify materials easily as they exist multiple times in the hierarchy.
The Scene Optimizer extension can be used to address this issue by:
De-duplicating all materials in the Prototype hierarchies.
Moving the de-duplicated materials under a single scope
/World/Looks
.
Stages with many instances and prototypes containing duplicated materials.
Modify Stage - Merge Meshes
Simplify and flatten the hierarchical complexity of the stage drastically, at the cost of addressability and memory.
It merges small meshes into bigger meshes and moves materials into /World/Looks.
De-instance
De-duplicate Materials
Move materials to
/World/Looks
De-instance
Merge under
/World/Geometry/merged
Delete hidden prims
Prune leaves
Set pivots
Architecture and Process Data with complex scene Hierarchies that can be simplified.
Modify Stage - Spatial Merge Meshes
Similar to the Merge Meshes preset, but merges all meshes regardless of materials based on a defined bounding volume. Users may want to change the size of the controls to yield different results of the merge operator.
This is a very effective preset which can drastically reduce the scene complexity.
Architecture and process data with complex scene hierarchies that can be simplified.
Scene Optimizer CLI#
Installation#
Scene Optimizer is called from Python with the extension enabled via a supplied Kit installation. The following are available options for usage:
Python Package
Kit App Template
Python Script executed in a Kit environment
Note
If a specific version of the the extension is needed, it can be specified when enabling in the flag for Kit: --enable 'omni.scene.optimizer.core-106.1.2'
Python Package#
As of Kit 106.0, the application is available via Python package.
pip install omniverse-kit
Kit App Template#
As of the Kit 106.0 release, this extension can be called from one of the Sample App Templates. Installing the base kit_base_editor
template will work for running the command line version of Scene Optimizer as dependencies will be pulled on first launch of the CLI command.
Running Scene Optimizer CLI#
In the examples for Python Package
and Kit App Template
, a Python script is needed to execute the Scene Optimizer operations. A Sample Python Script is provided in the documentation below and also takes a JSON configuration file as an argument.
Python Usage#
There is one primary command that is used to execute Scene Optimizer operations. The command takes an operation name and a dictionary of argumentss.
This command can be executed directly, or the standard approach is to execute it via Kit:
operationName = "merge"
args = { ... } # Define args here
omni.kit.commands.execute("SceneOptimizerOperation", operation=operationName, args=myArgs)
A single command context is used for Scene Optimizer operations, the command takes an operation name and a dictionary of arguments. The following can be used for the current context or to specify an alternate stage to run on.
The usdStageId
property accepts the value returned when the stage
is inserted into the UsdUtils.StageCache
.
context = omni.scene.optimizer.core.ExecutionContext()
context.usdStageId = UsdUtils.StageCache().Get().Insert(stage).ToLongInt()
context.generateReport = 1
context.captureStats = 1
omni.kit.commands.execute("SceneOptimizerOperation", operation=operationName, args=myArgs, context=context)
The commands SceneOptimizerListOperations
and SceneOptimizerListOperationArguments
can be used to query the registered operations and the arguments they accept.
args = {
"paths": [],
"reductionFactor": 50.0,
"maxMeanError": 0.0,
"guideDecimation": 0,
"pinBoundaries": false,
"cpuVertexCountThreshold": 100000,
"gpuVertexCountThreshold": 500000
}
context = omni.scene.optimizer.core.ExecutionContext()
context.usdStageId = UsdUtils.StageCache().Get().Insert(stage).ToLongInt()
context.generateReport = 1
context.captureStats = 1
omni.kit.commands.execute("SceneOptimizerOperation", operation="decimateMeshes", args=args)
# Passing the arguments via JSON
"""
Reads JSON data containing commands to execute via the Scene Optimizer.
:param str jsonFile: Either the path to a JSON file on disk, or a JSON string.
"""
j = '[
{
"operation": "decimateMeshes",
"paths": [],
"reductionFactor": 50.0,
"maxMeanError": 0.0,
"guideDecimation": 0,
"pinBoundaries": false,
"cpuVertexCountThreshold": 100000,
"gpuVertexCountThreshold": 500000
}
]'
myArgs = {"jsonFile": j}
omni.kit.commands.execute("SceneOptimizerJsonParser", context=context, args=myArgs)
Python Package#
python3 -m omni.kit_app --enable 'omni.scene.optimizer.core' --enable 'omni.usd' --exec '/path/to/python_script.py /path/to/input_dir /path/to/output_dir /path/to/json_config.json --input_glob=*.usd'
Kit App Template#
Linux
./kit --enable 'omni.scene.optimizer.core' --enable 'omni.usd' --exec '/path/to/python_script.py /path/to/input_dir /path/to/output_dir /path/to/json_config.json --input_glob=*.usd'
Windows
.\kit.exe --enable 'omni.scene.optimizer.core' --enable 'omni.usd' --exec '/path/to/python_script.py /path/to/input_dir /path/to/output_dir /path/to/json_config.json --input_glob=*.usd'
Note
Please use the kit
version inside the _build
directory of the applicable kit app template
Config File Description#
The config file is a JSON file that describes one or more optimizations to apply to the data.
Below is an example config file that first optimizes scene materials by replacing identical materials with a single material, then deduplicates geometry by replacing identical versions with geometry instances.
[
{
"operation": "optimizeMaterials",
"materialPrimPaths": [],
"optimizeMaterialsMode": 0
},
{
"operation": "deduplicateGeometry",
"meshPrimPaths": [],
"considerDeepTransforms": true,
"tolerance": 0.001,
"duplicateMethod": 2,
"fuzzy": false,
"useGpu": false,
"allowScaling": false,
}
]
Sample Python Script#
The sample python script can be used to batch process directories of USD files with varying config files:
"""Script to batch process USD files with Scene Optimizer operations"""
import argparse
from pathlib import Path
import omni
import omni.usd
from pxr import UsdUtils
def optimize_files_in_dir(input_dir, output_dir, optimizer_json, input_glob="*"):
"""Runs optimization configurations over all USD files in single directory"""
input_files = list(Path(input_dir).glob(input_glob))
for i, input_file in enumerate(input_files):
output_file = Path(output_dir, input_file.name)
output_file = str(output_file)
input_file = str(input_file)
if not omni.usd.get_context().open_stage(input_file):
raise ValueError(f"Could not open file {input_file}")
stage = omni.usd.get_context().get_stage()
# Check to see if native prims exist along side payloads or references
for prim in stage.Traverse():
# check to see if prim is a payload or reference
if not (prim.HasAuthoredPayloads() or prim.HasAuthoredReferences()):
omni.log.warn(f"++ [{i}/{len(input_files)}] Optimizing {input_file} > {output_file}")
context = omni.scene.optimizer.core.ExecutionContext()
context.usdStageId = UsdUtils.StageCache().Get().Insert(stage).ToLongInt()
context.generateReport = 1
context.captureStats = 1
# If a config file exists for a specific file use that instead of default input
optimizer_json_file = Path(input_dir, Path(input_file).stem + ".json")
if optimizer_json_file.is_file():
myArgs = {"jsonFile": str(optimizer_json_file)}
else:
myArgs = {"jsonFile": optimizer_json}
omni.kit.commands.execute("SceneOptimizerJsonParser", context=context, args=myArgs)
# Export stage to a new file
stage.GetRootLayer().Export(output_file)
# Close each stage to avoid kit crashes
omni.usd.get_context().close_stage()
break
def main():
parser = argparse.ArgumentParser(
description="Optimize USD files in a directory.",
epilog="Example: /path/to/kit --enable 'omni.scene.optimizer.core' --enable 'omni.usd' --exec 'python_script_name.py /path/to/input /path/to/output /path/to/config.json --input_glob=*.usd' ",
)
parser.add_argument("input_dir", help="Directory containing input files")
parser.add_argument("output_dir", help="Directory to save optimized files")
parser.add_argument("json_config", help="JSON configuration file for the optimizer")
parser.add_argument("--input_glob", default="*", help="Glob pattern to match input files (default: *)")
args = parser.parse_args()
optimize_files_in_dir(args.input_dir, args.output_dir, args.json_config, input_glob=args.input_glob)
if __name__ == "__main__":
main()
Integrations#
The scene optimizer is also available in the following integrations:
- Standalone Application
A command line tool that offers faster load times and is convenient for scripting to process multiple files.
- CAD Converter
A convenient tool for translating CAD data to USD in Kit applications or services.
- Revit Connector
A direct Revit integration that utilizes the core optimization libraries found across scene optimizer operations.
- Creo Connector
A direct Creo integration that utilizes the core optimization libraries found across scene optimizer operations.