OpenUSD: Advanced Patterns and Common Gotchas.
28th March 2026
This post covers deeper OpenUSD concepts: schemas, rendering rules, performance patterns, and the gotchas that surprise people.
1. Reference-Payload Pattern
The most important structural pattern in production USD pipelines is splitting every asset into two layers:
| Layer | Always loaded? | What goes here |
|---|---|---|
| Reference layer | Yes | Composition arcs, variant set definitions, asset metadata (kinds, assetInfo), asset structure |
| Payload layer | On demand | Heavy geometry, vertex data, subdivision surfaces |
Lofting means you promote information from the payload layer to the reference layer. That information is then visible without loading the payload. A scene browser can show asset names, thumbnails, and bounding boxes without loading any geometry.
def Xform "Robot" (
# Reference layer — always loaded, lightweight
kind = "component"
assetInfo = { string identifier = "robot_v3" }
# Payload — only loaded when needed
prepend payloads = @./robot_geometry.usdc@
)
{
# Lofted data (promoted from payload, visible without loading)
float3[] extent = [(-0.5, 0, -0.5), (0.5, 1.2, 0.5)]
}
2. Schemas — Typed vs API
There are two different categories of USD schemas:
| Typed (IsA) | API (HasA) | |
|---|---|---|
| Purpose | Defines what a prim IS | Adds behaviour/properties to any prim |
| Per prim | Only ONE per prim | Multiple allowed |
| Inheritance | Can chain (Mesh → Gprim → Xformable) | CANNOT inherit from other API schemas |
| Examples | Mesh, Xform, Scope, DomeLight | RigidBodyAPI, CollectionAPI, PrimvarAPI |
API schemas have three sub-types:
- Non-applied: you use it in code without applying it to a prim
- Single-apply: you apply it once per prim (e.g.
PhysicsRigidBodyAPI) - Multiple-apply: you apply it multiple times with different instance names (e.g.
CollectionAPI:geometry,CollectionAPI:lights)
Concrete vs Abstract
- Concrete: you can create prims of this type directly. Examples:
Mesh,Xform,Scope - Abstract: you cannot instantiate this type directly. You must subclass it. Examples:
Xformable,Imageable,Gprim
3. Codeful vs Codeless Schemas
When you build custom schemas, you have two implementation options:
| Codeless | Codeful | |
|---|---|---|
| Implementation | plugInfo.json only, no C++ | Generates C++ and Python bindings |
| Portability | Works across USD versions | Must recompile per USD version |
| Developer experience | Limited autocomplete/typing | Full IDE support |
| Use when | Multiple DCCs with different USD versions | Single controlled USD version |
To register plugins, set the PXR_PLUGINPATH_NAME environment variable to your plugInfo.json directory.
4. Plugin Types
| Plugin | Requires compilation? | How to define |
|---|---|---|
| Metadata plugin | No | plugInfo.json only |
| Variant fallback | No | plugInfo.json only |
| Asset resolver | Yes | C++ code |
| Custom schema | Optional | usdGenSchema + plugInfo.json |
Variant fallback only activates when no variant selection is authored. If a selection exists (even an empty string), USD ignores the fallback.
5. Attributes vs Relationships
Attributes hold values, such as float, int, color3f, and matrix4d. You can animate them with time samples.
Relationships are pointers to other prims or attributes. They do nothing on their own. Runtime code (like Hydra for materials, or physics engines) must interpret them. A material binding relationship means nothing without a renderer that knows how to follow it.
API distinctions to know:
# Returns ALL properties including schema defaults
prim.GetProperties()
# Returns ONLY what you explicitly authored
prim.GetAuthoredProperties()
# Returns an ATTRIBUTE OBJECT — not the value!
attr = prim.GetAttribute("radius")
value = attr.Get() # must call .Get() to get the actual value
6. SdfValueTypeNames — Key Types
| Type | What it is |
|---|---|
token | Like string but interned for performance — use for repeated values like kind, purpose, visibility |
asset | Reference to an external file — goes through the asset resolver |
matrix4d | 4×4 transformation matrix |
point3f | Position in space (role-based — semantically different from a vector) |
normal3f | Surface normal (role-based) |
color3f | RGB colour (role-based) |
Role-based types have the same underlying data as plain vectors. But they also carry semantic meaning, and tools and renderers can respond to that meaning differently.
7. Rendering — The Rules That Catch People
Minimum required to render a mesh: only these three things.
faceVertexCountsfaceVertexIndicespoints
You do not need materials, lights, or xforms.
Visibility has only TWO valid values:
inherited(default): the prim inherits visibility from its parentinvisible: the prim is hidden
There is no “visible” token. To make something visible again after hiding it, set it back to inherited.
Purpose has four values:
| Purpose | Rendered? | Use for |
|---|---|---|
default | Always | Normal geometry |
render | Render passes | Highest quality geometry |
proxy | Viewport | Lightweight stand-in |
guide | Never | Rig helpers, calculations only |
Primvar interpolation modes: the number of required values differs by mode.
| Mode | Value count | Behaviour |
|---|---|---|
constant | 1 | Entire mesh gets one value |
uniform | Number of faces | One value per face, no interpolation |
vertex | Number of unique points | Per vertex, surface-following interpolation |
faceVarying | Sum of all face vertex counts | Per vertex per face — allows sharp edges on UV seams |
vertex and varying have the same element count but differ on curved surfaces.
Materials beat primvars: a bound material’s colour overrides primvars:displayColor.
Material binding strength:
weakerThanDescendants(default): a child’s material binding wins over its parent’sstrongerThanDescendants: the parent’s binding wins and overrides the children
Lights do not inherit from UsdGeomImageable, so they have no visibility control through the standard visibility attribute.
8. Time Samples
Time sample priority for timeCodesPerSecond (highest to lowest):
- Session layer
timeCodesPerSecond - Root layer
timeCodesPerSecond - Session layer
framesPerSecond - Root layer
framesPerSecond - Fallback: 24
Time samples completely override default and local property values. They do not blend with them. If an attribute has any time samples, USD ignores the non-time-sampled value at any time code where a sample exists.
Time offset formula: (sourceTimeCode + offset) × scale
9. SDF Change Blocks
Sdf.ChangeBlock() batches multiple edits. It fires change notifications once at the end instead of after every individual edit. This is a significant performance win in interactive applications like Omniverse Kit.
from pxr import Sdf
with Sdf.ChangeBlock():
attr1.Set(1.0) # safe — modifying existing values
attr2.Set("hello") # safe
# DO NOT create new prims inside a change block — unsafe!
Safe inside a change block: you change existing attribute values. Unsafe: you create new prims.
10. Hierarchy Rules
- A
Meshcannot be the parent of anotherMesh. Use anXformas the parent. - Mark only
Xforms as instanceable. If you make aMeshinstanceable, all instances stack at the same position. - All ancestors of a
componentkind prim must begrouporassembly. Untyped prims in that chain break the model hierarchy chain.
11. Common Gotchas
- There is no
"visible"visibility token. Use"inherited"to un-hide. - Components cannot contain other components
- API schemas cannot inherit from other API schemas
- Only one typed schema per prim — multiple API schemas are fine
- Relationships do nothing without runtime code to interpret them
- Sublayers do not auto-correct orientation or scale. References and payloads do.
- Use codeless schemas when your pipeline has multiple DCCs on different USD versions
- Variant fallback only applies when no selection is authored at all
GetProperties()≠GetAuthoredProperties(). The former includes schema defaults.- Materials beat primvars: material colour wins over
primvars:displayColor - Time samples override default/local values completely
extentis for bounding box calculations, not for rendering- Inherits = broadcast (base class changes propagate). Specializes = OOP-like (derived keeps its own override).
More recent articles
- pi-durable, from the ground up - 20th October 2026
- Hermes Agent: The Self-Improving Story - 12th June 2026
- Claude Code Dynamic Workflows: Inside Out - 29th May 2026