Mesh merge tool#
Deprecated since version 6.0: The standalone isaacsim.util.merge_mesh extension is deprecated and will be removed in a future release. Use the Scene Optimizer merge operation instead, through the URDF/MJCF importer’s Merge Mesh option, the Scene Optimizer panel (Window > Utilities > Scene Optimizer), the Asset Transformer MergeMeshRule, or by calling the operation directly from Python.
About#
Mesh merging combines multiple visual geometry prims that share a common rigid-body parent into a single mesh. Reducing the number of geometry prims lowers draw-call overhead and improves both rendering and physics performance.
NVIDIA Isaac Sim performs mesh merging through the Scene Optimizer Kit extension (omni.scene.optimizer.core). The Scene Optimizer merge operation preserves materials as GeomSubset entries on the resulting mesh, supports vertex deduplication, and can group meshes spatially.
Four integration paths expose the Scene Optimizer merge operation in NVIDIA Isaac Sim:
Integration |
Where to use it |
Best for |
|---|---|---|
URDF / MJCF importer |
File > Import > Merge Mesh checkbox |
One-shot import-time merging of robot assets |
Scene Optimizer panel |
Window > Utilities > Scene Optimizer |
Interactive merging on existing assets, ad-hoc presets |
Asset Transformer profile |
Asset Transformer > |
Repeatable, version-controlled asset pipelines |
Python API |
|
Custom scripts and headless asset preparation |
Using the URDF / MJCF importer#
Both the URDF and MJCF importers expose a Merge Mesh checkbox under the Options section of the importer UI. Enabling this option runs three Scene Optimizer operations in sequence on the converted USD stage:
meshCleanup— merges duplicate vertices, removes degenerate faces, fixes non-manifold geometry.generateNormalsandgenerateProjectionUVs— regenerates vertex normals and projected UVs.merge— for each rigid body in the stage, merges all child visual mesh prims (those withdefaultorrenderpurpose) into a single mesh under that body.
The importer determines merge groups automatically by traversing each UsdPhysics.RigidBodyAPI prim and collecting its child geometry, stopping at nested rigid-body boundaries. No additional configuration is required.
Refer to URDF Importer Extension and MJCF Importer Extension for the full importer workflows.
Using the Scene Optimizer UI#
The Scene Optimizer extension ships its own UI panel that exposes the merge operation directly, with no intermediate Asset Transformer rule. Use this path for one-off interactive merges on an existing stage, or to combine merge with other Scene Optimizer operations such as deduplication and material consolidation.
Use the Scene Optimizer panel to merge meshes interactively.
Enable the Scene Optimizer extension. Scene Optimizer is not enabled by default in the standard NVIDIA Isaac Sim experiences. Open the Extension Manager (Window > Extensions), search for
omni.scene.optimizer.bundle, and toggle it on. The bundle pulls in thecore,ui,analysis, andvalidatorsextensions.Load the asset to merge. Use File > Open to load the USD into the active stage.
Open the Scene Optimizer panel. Select Window > Utilities > Scene Optimizer.
Choose a workflow:
To use a bundled preset, click Load Preset and pick one of the merge-related presets:
Modify Stage - Merge Meshes — flatten the hierarchy and merge meshes by material into
/World/Geometry/merged.Modify Stage - Spatial Merge Meshes — merge all meshes regardless of material based on a spatial bounding volume; useful for very dense scenes.
Modify Instances - Merge Meshes — merge meshes inside each prototype while preserving the instance hierarchy. Best when the asset uses scenegraph instancing.
To merge a hand-picked set of meshes, click Add Scene Optimizer Process and, under the Geometry group, select Merge Static Meshes (the operation registered as
merge). Configure its fields:Static Meshes to Merge (
meshPrimPaths): enter prim paths or path expressions (for example,/World/Robot/left_wheel//Mesh*— the leading//matches descendants at any depth underleft_wheel). Click the pencil icon to pick paths from the stage, or click Add with prims selected in the viewport. Leave it empty to process every eligible mesh on the stage.Keep Materials Separate (
considerMaterials, default off): when off, meshes with differing materials are combined into a single mesh that carries a per-materialGeomSubsetfor each material. When on, a separate merged mesh prim is created for each material. Leave it off to reproduce the legacy Combine Materials behavior.Compute Display Colors (
materialAlbedoAsVertexColors, default off): set each vertex’s display color and opacity from the bound material’s albedo.Original Mesh Handling (
originalGeomOption, default Delete): what to do with the source meshes after merging —Ignore(0),Delete(1),Deactivate(2), orHide(3).Merge Boundary (
mergePoint, default Stage): the boundary within which meshes are grouped and merged —Stage(0),Parent Xform(1), theKind:levelsAssembly(2),Group(3),Component(4),Model(5),Subcomponent(6),Root Prim(7),Parent Prim(8), orOriginal Prim(9). UseRoot Primor aKind:level to keep merges from crossing model boundaries.Output Name (
rootPath): optional name/path for the merged result; defaults toMerged_0.Strict Attribute Mode (
considerAllAttributes, default off) and Allow Single Meshes (allowSingleMeshes, default off): leave at their defaults for typical robot assets.Spatial Clustering Mode (
spatialMode, default None):None(0),Bounding Box(1),Vertex Count(2), orCoincident Boundary Vertices(3). Selecting a mode reveals its parameters — Spatial Threshold / Spatial Max Size (Bounding Box), Spatial Vertex Count (Vertex Count), or Boundary Tolerance / Minimum Shared Vertices (Coincident Boundary Vertices). Leave at None unless you need spatial grouping.
Add prerequisite cleanup steps (optional but recommended). Click Add Scene Optimizer Process twice more and add Mesh Cleanup (
meshCleanup) followed by Generate Normals (generateNormals). Drag the handles in the top-left of each process card so they execute before Merge Static Meshes. This matches the pipeline the URDF/MJCF importer runs internally.Execute and verify.
Click Execute All. The console log reports the operations as they run.
Inspect the stage. Each merge group is replaced by a single mesh prim named after the configured Output Name. If Original Mesh Handling was set to
Deactivate, the source meshes remain in the stage as deactivated prims (Deleteremoves them entirely, whileIgnoreleaves them untouched).Save the result with File > Save As.
Save the configuration for reuse (optional). Click Save Preset to write the current process stack to a JSON file. The same JSON can be loaded later from this UI, or fed to the Scene Optimizer CLI for batch processing.
Note
The Scene Optimizer UI executes operations directly on the active stage in memory. Save the stage explicitly after merging — the panel does not write to disk on its own.
The Scene Optimizer panel reads and writes its own JSON preset format. A minimal preset that mirrors the importer’s pipeline (cleanup, normals + UVs, merge) looks like this:
[
{
"operation": "meshCleanup",
"paths": [],
"mergeVertices": true,
"tolerance": 0.0,
"mergeBoundaries": true,
"mergeNeighbors": true,
"contractDegenerateEdges": true,
"removeDegenerateFaces": true,
"removeIsolatedVertices": true,
"removeDuplicateFaces": true,
"makeManifold": true
},
{
"operation": "generateNormals",
"paths": [],
"binding": 0,
"sharpnessAngle": 60.0,
"gpuThreshold": 500000
},
{
"operation": "generateProjectionUVs",
"paths": [],
"projectionType": 4,
"useWorldSpaceScales": true,
"scaleFactor": 0.01,
"overwriteExisting": true
},
{
"operation": "merge",
"meshPrimPaths": ["/World/Robot/left_wheel//Mesh*"],
"considerMaterials": false,
"materialAlbedoAsVertexColors": false,
"originalGeomOption": 1,
"mergePoint": 0,
"rootPath": "",
"considerAllAttributes": true,
"allowSingleMeshes": false,
"spatialMode": 0,
"spatialThreshold": 10.0,
"spatialMaxSize": 0.0,
"spatialVertexCount": 10000,
"spatialDebug": false
}
]
Edit meshPrimPaths to target the meshes to merge. Use USD path expressions — the // operator matches descendants at any depth, so /World/Robot//Mesh* finds every prim named Mesh* anywhere under /World/Robot.
Load this file via the Scene Optimizer panel’s Load Preset > Browse… button.
Refer to the Scene Optimizer extension documentation for the full operation catalog, the bundled preset descriptions, and the Scene Optimizer CLI usage.
Using the asset transformer#
If you need to chain mesh merging with other USD restructuring rules — schema routing, geometry deduplication, material routing, interface generation — wrap the merge step in an Asset Transformer profile. The Asset Transformer ships a Merge Mesh profile (source/extensions/isaacsim.asset.transformer.rules/data/merge_mesh.json) that runs the same rigid-body-aware merge as the importer:
{
"profile_name": "Merge Mesh",
"version": "1.0",
"rules": [
{
"name": "Merge Meshes",
"type": "isaacsim.asset.transformer.rules.isaac_sim.merge_mesh.MergeMeshRule",
"destination": "",
"params": {},
"enabled": true
}
],
"interface_asset_name": null,
"output_package_root": null,
"flatten_source": false,
"base_name": null
}
The MergeMeshRule walks all rigid bodies in the stage and applies the Scene Optimizer merge operation to each body’s child visual meshes — no parameters required.
Chain it with GeometriesRoutingRule and MaterialsRoutingRule for a full cleanup + merge + dedupe pipeline:
{
"profile_name": "Cleanup + Merge + Dedupe",
"version": "1.0",
"rules": [
{
"name": "Merge Meshes",
"type": "isaacsim.asset.transformer.rules.isaac_sim.merge_mesh.MergeMeshRule",
"destination": "",
"params": {},
"enabled": true
},
{
"name": "Route Geometries",
"type": "isaacsim.asset.transformer.rules.perf.geometries.GeometriesRoutingRule",
"destination": "payloads",
"params": {
"geometries_layer": "geometries.usd",
"instance_layer": "instances.usda",
"deduplicate": true
},
"enabled": true
},
{
"name": "Route Materials",
"type": "isaacsim.asset.transformer.rules.perf.materials.MaterialsRoutingRule",
"destination": "payloads",
"params": {
"materials_layer": "materials.usda",
"download_textures": true
},
"enabled": true
}
],
"interface_asset_name": null,
"output_package_root": null,
"flatten_source": false,
"base_name": null
}
Load the profile via Tools > Robotics > Asset Editors > Asset Transformer > Load Preset, set the Output Directory, and click Execute Actions. Refer to Asset Transformer Rules Reference for the complete rule catalog and to Asset Transformer Tutorials for Asset Transformer GUI walkthroughs.
Using the Python API#
The isaacsim.asset.importer.utils.merge_mesh_utils module wraps the Scene Optimizer operations used by the importer. Use it for headless asset preparation, batch processing, or custom asset “massaging” that does not fit the importer or transformer flows.
The code blocks below are taken from a single runnable script, docs/isaacsim/snippets/robot_setup/merge_mesh.py, which you can execute with Isaac Sim’s python.sh. Pass --test to import the bundled carter.urdf test asset and exercise every snippet end-to-end.
Note
The omni.scene.optimizer.core extension must be enabled before calling these helpers. Standalone scripts must enable it explicitly:
import omni.kit.app
omni.kit.app.get_app().get_extension_manager().set_extension_enabled_immediate("omni.scene.optimizer.core", True)
Available helpers:
Function |
Purpose |
|---|---|
|
Run |
|
Run |
|
Walk all rigid bodies and merge each body’s child visual meshes (returns the number of merged groups). |
|
Merge an explicit list of mesh prim paths into a single mesh. |
Example: full importer-equivalent pipeline applied to an existing stage
from isaacsim.asset.importer.utils import merge_mesh_utils
from pxr import Usd
# stage_path = "/path/to/robot.usd"
stage = Usd.Stage.Open(stage_path)
merge_mesh_utils.clean_mesh_operation(stage)
merge_mesh_utils.generate_mesh_uv_normals_operation(stage)
merged_groups = merge_mesh_utils.merge_meshes_operation(stage)
print(f"Merged {merged_groups} rigid-body mesh group(s)")
stage.Save()
Example: merge a hand-picked set of meshes (replaces the legacy “select prims, click Merge” workflow)
from isaacsim.asset.importer.utils import merge_mesh_utils
from pxr import Usd
# stage_path = "/path/to/asset.usd"
# mesh_paths = [
# "/World/Jetbot/left_wheel/visual_0",
# "/World/Jetbot/left_wheel/visual_1",
# "/World/Jetbot/left_wheel/visual_2",
# ]
stage = Usd.Stage.Open(stage_path)
merge_mesh_utils.merge_mesh(stage, mesh_paths)
stage.Save()
The first prim in the list is used as the merge root and origin, matching the legacy tool’s “first selection wins” behavior.
Scene Optimizer (now USD Optimize) operation reference#
For finer control, call usd_optimize.core operations directly. Build a UsdOptimizeCore instance and an ExecutionContext bound to your stage, then dispatch operations by name with a configuration dict:
from pxr import Usd
from usd_optimize.core import ExecutionContext, UsdOptimizeCore
# stage_path = "/path/to/asset.usd"
# mesh_paths = ["/World/A", "/World/B", "/World/C"]
stage = Usd.Stage.Open(stage_path)
context = ExecutionContext()
context.set_stage(stage)
context.generateReport = 0
context.captureStats = 0
core = UsdOptimizeCore.getInstance()
core.executeOperation(
"merge",
context,
{
"meshPrimPaths": list(mesh_paths),
"considerMaterials": False,
"materialAlbedoAsVertexColors": False,
"originalGeomOption": 1,
"mergePoint": 0,
"rootPath": mesh_paths[0],
"considerAllAttributes": True,
"allowSingleMeshes": False,
"spatialMode": 0,
"spatialThreshold": 10.0,
"spatialMaxSize": 0.0,
"spatialVertexCount": 10000,
"spatialDebug": False,
},
)
stage.Save()
The default configurations the NVIDIA Isaac Sim importer uses are listed below for reference.
MESH_CLEANUP_CONFIG = {
"paths": [],
"mergeVertices": True,
"tolerance": 0.0,
"mergeBoundaries": True,
"mergeNeighbors": True,
"contractDegenerateEdges": True,
"removeDegenerateFaces": True,
"removeIsolatedVertices": True,
"removeDuplicateFaces": True,
"makeManifold": True,
}
GENERATE_NORMALS_CONFIG = {
"paths": [],
"binding": 0,
"sharpnessAngle": 60.0,
"gpuThreshold": 500000,
}
GENERATE_PROJECTION_UVS_CONFIG = {
"paths": [],
"projectionType": 4,
"useWorldSpaceScales": True,
"scaleFactor": 0.01,
"overwriteExisting": True,
}
MERGE_CONFIG = {
"meshPrimPaths": [],
"considerMaterials": False,
"materialAlbedoAsVertexColors": False,
"originalGeomOption": 1,
"mergePoint": 0,
"rootPath": "",
"considerAllAttributes": True,
"allowSingleMeshes": False,
"spatialMode": 0,
"spatialThreshold": 10.0,
"spatialMaxSize": 0.0,
"spatialVertexCount": 10000,
"spatialDebug": True,
}
The importer leaves considerMaterials at its default of False. With considerMaterials = False the operation combines meshes that use different materials into a single mesh and preserves each material as a GeomSubset — this is the equivalent of the legacy tool’s Combine Materials option. Set considerMaterials = True to instead emit a separate merged mesh prim per material (for example, when downstream material routing such as the asset transformer’s MaterialsRoutingRule expects one mesh per material).
Migrating from the legacy mesh merge tool#
Legacy option |
Scene Optimizer equivalent |
|---|---|
Source Prim (first selection = origin) |
Pass mesh paths to |
Clear Parent Transform |
Choose the Merge Boundary ( |
Deactivate source assets |
Set Original Mesh Handling ( |
Combine Materials |
Leave |
Selecting an empty Xform first to set origin |
Use Output Name ( |
See also#
URDF Importer Extension — URDF importer with built-in Merge Mesh option.
MJCF Importer Extension — MJCF importer with built-in Merge Mesh option.
Asset Transformer — Rule-based asset transformation framework.
Asset Transformer Rules Reference —
MergeMeshRulereference and other available rules.Scene Optimizer extension documentation — Full operation reference for
omni.scene.optimizer.core.