name: deduplicate-hierarchies description: Collapse duplicate prim hierarchies into instanceable internal references. Use when deduplicating subtrees or folding repeated prims into prototypes. version: "1.0.0" allowed-tools: Bash, Read metadata: author: NVIDIA Corporation tags: [usd, deduplication, hierarchy, instancing]
Deduplicate Hierarchies
Invocation. This is the
deduplicate-hierarchiesskill. Claude Code also exposes it as the alias/deduplicate-hierarchies. Codex and other agents invoke it by name. Don't reference the alias-only form when writing for a non-Claude agent.Python invocation. Examples below use
python3(POSIX). On Windows usepy -3(the Python launcher) or the build's bundled interpreter at_build\target-deps\python\python.exe.Windows shell. Snippets target PowerShell. cmd.exe equivalents are obvious —
$Var→%VAR%, backtick line continuation →^.repo.batis cmd-style internally but can be invoked from any shell.
Safety note. Duplicates are identified by subtree shape, prim types, and authored property names, then refined by verifying all property values match (excluding xformOps on the root prim, which represent placement). Floating-point values — scalars, vectors, matrices (including descendant
xformOp:transform), quaternions, and arrays of them — are compared withintolerance; integer/topology, string, token and bool values always require an exact match. Descendant transforms that differ by more thantolerancestill block the merge. Settolerance=0for a bitwise-exact compare. It will only merge prims that are truly identical (within tolerance). This is safe on any asset. Thepathsargument can scope the run to a known-safe subtree.
Find duplicate prim hierarchies (level by level under the default prim) and replace duplicate subtrees with internal instanceable references — duplicates become refs to the first instance (the prototype). All geometry stays in the main stage file.
This is not the same as Usd Optimize's deduplicateGeometry
operation, which only handles individual meshes. This skill works on
entire prim hierarchies matched by structure.
What this skill covers
Sections below are ordered for execution — read past Step 3 before
concluding saving or pipeline details are missing. Search for keywords like
tolerance, paths, deduplicateGeometry, analysisMode, ignoreShaderOutputs,
or GetRootLayer().Export to jump.
- Inputs — asset path and optional
--pathsscoping. - Step 1 — validate the USD path exists and has a
.usd*suffix. - Step 2 — verify
deduplicateHierarchiesis registered in the build. - Step 3 — canonical two-op pipeline (
deduplicateHierarchiesthendeduplicateGeometry), root-layer export, environment pointers. - Step 4 — what to report after a successful run (and analysis mode for detail).
- Pre-flight checks — default prim requirement.
- Common pitfalls — skipped material-ish prims, refs/payloads behavior.
- Verification — instanceable flags, prototype paths, flat export pitfalls.
- See also — operation guides (
deduplicateHierarchies,deduplicateGeometry). - Purpose — one-paragraph rationale.
- Prerequisites — build, default prim, output path conventions.
- Limitations — what the op does not merge or flatten.
- Troubleshooting — symptom / cause / fix table.
Companion skills: run-operations (general op driver), prebuilt-package /
.agents/skills/build/SKILL.md (runtime setup), debug-operation (failing ops).
Inputs
| Input | Required | Default | Meaning |
|---|---|---|---|
<asset> |
yes | — | Path to a .usd / .usda / .usdc / .usdz stage file. |
--paths <prim path,...> |
no | (whole stage) | Restrict the search to one or more subtree roots. |
If no asset path is provided, ask:
"Which USD file should I deduplicate? Please provide the full path."
Step 1 — Validate the input
A path that doesn't exist or doesn't end in .usd* should be rejected
before any work.
Step 2 — Verify the operation is available
The operation is invoked via Usd Optimize's standalone runner:
usd_optimize.core.scripts.standalone.execute_commands_from_json.
No Kit dependency.
Confirm the operation is registered in the build:
from usd_optimize.core import UsdOptimizeCore
print("deduplicateHierarchies" in UsdOptimizeCore.getInstance().getOperations())
(POSIX) python3 -c "...", (Windows PowerShell) py -3 -c "..." or
_build\target-deps\python\python.exe -c "...".
Step 3 — Run the operation
Assemble a config for execute_commands_from_json. The canonical pipeline
is two steps: our hierarchy-level dedup, then deduplicateGeometry for any
remaining per-mesh duplicates that didn't fall out of the hierarchy pass.
Pair them in a single config so each step sees the previous step's edits.
Path strings below use forward slashes — Python accepts those on Windows and POSIX alike. Replace the placeholders with absolute or repo-relative paths that fit the user's environment; do not embed Windows drive letters or raw strings unless the user has supplied them explicitly.
import json
from pxr import Usd
from usd_optimize.core.scripts.standalone import execute_commands_from_json
INPUT_USD = "path/to/asset.usd"
OUTPUT_USD = "path/to/asset_deduped.usd"
config = [
# 1. Hierarchy-level dedup: replace duplicate prim subtrees with
# instanceable internal references to the first instance.
{
"operation": "deduplicateHierarchies",
# "tolerance": 0.001, # float tolerance for vertex drift
# "paths": ["/World/MySubtree"], # optional subtree restriction
# "ignoreShaderOutputs": True, # skip outputs:* during value
# # comparison; default True
},
# 2. Per-mesh dedup: catch identical mesh duplicates that the
# hierarchy pass didn't fold (different parents, same geometry).
# duplicateMethod=2 -> Instanceable Reference.
{
"operation": "deduplicateGeometry",
"duplicateMethod": 2,
"tolerance": 0.001,
},
]
# Opt in to per-level / per-group logging by prepending:
# {"operation": "executionContext", "verbose": True}
# (context flag, not an op argument)
stage = Usd.Stage.Open(INPUT_USD)
ok = execute_commands_from_json(stage, json.dumps(config))
if not ok:
raise RuntimeError("deduplicateHierarchies pipeline failed — check Usd Optimize log")
# IMPORTANT: save via the root layer, NOT stage.Export().
# stage.Export() flattens the composed stage and rewrites Usd-instance
# prototype prims to synthetic root-level names like /Flattened_Prototype_N,
# which is technically equivalent but loses the authored prototype paths.
# Root-layer export preserves them.
stage.GetRootLayer().Export(OUTPUT_USD)
For a hierarchy-only run (skip per-mesh dedup, faster, less aggressive),
drop the second config entry. Use this when the user has already run
deduplicateGeometry upstream, or only wants the assembly-level rollup.
Environment setup (Usd Optimize on PYTHONPATH, native libs on PATH/LD_LIBRARY_PATH)
is the same as for any other Usd Optimize pipeline — defer to
.agents/skills/build/SKILL.md for a source-tree build or to the
prebuilt-package skill for a packaged runtime. Don't duplicate environment
setup here.
Step 4 — Report
After the run completes, summarise for the user:
- Number of prototype groups found and total duplicate prims replaced.
If the user asks follow-ups about which prims were affected, run the
operation in analysis mode to retrieve the {prototype: [duplicates]} map.
Pre-flight checks
- Default prim exists. The operation traverses from the stage's default
prim; no default prim → no duplicates → no work done. Open the file
briefly and confirm
stage.GetDefaultPrim()is valid.
Common pitfalls
- Material-related prims are skipped.
Material,Shader,NodeGraph,GeomSubsetprims, theLooks/Materialsscopes, and prims with texture-name prefixes (Diffuse,Specular, etc.) are intentionally excluded from the duplicate scan. If a hierarchy you expected to be deduped is being skipped, check it doesn't fall under one of those predicates. - Existing references / payloads. Prims that already have authored references or payloads are excluded from the duplicate group. They count against the "matched" set so they don't re-appear at deeper levels.
Verification
- Hierarchy reduced. Count subtree-rooted prim groups before and after. The post-run count of distinct subtree shapes should be lower.
- Instanceable flag set. Every duplicate prim should report
prim.IsInstanceable() == Trueand have one internal reference whose target is the prototype path. - Prototype names preserved. Inspect the saved layer (e.g.
usdcator open the file in a text-friendly viewer for.usda/.usdASCII). Prototype prims should keep their authored names. Synthetic root-level names like/Flattened_Prototype_Nindicate the file was saved withstage.Export()instead ofstage.GetRootLayer().Export()— see Step 3. - Analysis mode. Run the operation in analysis mode to get the
{prototype: [duplicates]}map without mutating the stage. Spot-check a couple of the reported paths inusdview.
See also
.agents/operations/deduplicateHierarchies.md— the C++ operation guide (parameter reference, starting configs)..agents/operations/deduplicateGeometry.md— per-mesh deduplication, the finer-grained sibling.
Purpose
Reduce stage size and load time by collapsing duplicate prim hierarchies (whole subtrees) into instanceable internal references. The first occurrence becomes the prototype; subsequent identical subtrees are replaced with references to it. Matching is by structural hash plus a property-value comparison — safe on any asset.
Prerequisites
- A built repo (
./repo.sh buildorrepo.bat build) so thededuplicateHierarchiesoperation is registered. - A USD asset with a default prim set — the operation traverses from the default prim, so a stage without one is a silent no-op.
- A target output path. The skill never overwrites the input file unless the user explicitly asks.
Limitations
- Material-related prims (
Material,Shader,NodeGraph,GeomSubset, theLooks/Materialsscopes, andDiffuse*/Specular*-prefixed prims) are intentionally skipped from the duplicate scan. - Prims with already-authored references or payloads are excluded from grouping — they count as "matched" so they don't reappear at deeper levels but are not themselves replaced.
- This skill does not flatten composition arcs. Run an upstream flatten if you need a self-contained output.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Operation runs but reports 0 prototype groups | Stage has no default prim, or all subtrees are unique. | Set a default prim (stage.SetDefaultPrim(...)) and re-run. Confirm with analysisMode: 1 to see the candidate map. |
Output uses synthetic /Flattened_Prototype_N paths |
Saved via stage.Export() instead of stage.GetRootLayer().Export(). |
Use root-layer export — see Step 3. |
| Fewer duplicates found than expected | Floating-point drift from re-export or tessellation may push otherwise-identical subtrees out of bitwise match. | Increase tolerance (only affects float arrays and scalar float/double; integer topology always requires exact match). |
RuntimeError: deduplicateHierarchies pipeline failed |
Operation rejected the config (bad arg key) or hit a USD I/O error. | Check the Usd Optimize log; verify argument keys against .agents/operations/deduplicateHierarchies.md. |
| Per-mesh duplicates remain after the run | This op only handles whole hierarchies. | Pair with deduplicateGeometry (already in the canonical pipeline shown in Step 3). |