Akshay Parkhi's Weblog

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:

LayerAlways loaded?What goes here
Reference layerYesComposition arcs, variant set definitions, asset metadata (kinds, assetInfo), asset structure
Payload layerOn demandHeavy 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)
PurposeDefines what a prim ISAdds behaviour/properties to any prim
Per primOnly ONE per primMultiple allowed
InheritanceCan chain (Mesh → Gprim → Xformable)CANNOT inherit from other API schemas
ExamplesMesh, Xform, Scope, DomeLightRigidBodyAPI, 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:

CodelessCodeful
ImplementationplugInfo.json only, no C++Generates C++ and Python bindings
PortabilityWorks across USD versionsMust recompile per USD version
Developer experienceLimited autocomplete/typingFull IDE support
Use whenMultiple DCCs with different USD versionsSingle controlled USD version

To register plugins, set the PXR_PLUGINPATH_NAME environment variable to your plugInfo.json directory.

4. Plugin Types

PluginRequires compilation?How to define
Metadata pluginNoplugInfo.json only
Variant fallbackNoplugInfo.json only
Asset resolverYesC++ code
Custom schemaOptionalusdGenSchema + 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

TypeWhat it is
tokenLike string but interned for performance — use for repeated values like kind, purpose, visibility
assetReference to an external file — goes through the asset resolver
matrix4d4×4 transformation matrix
point3fPosition in space (role-based — semantically different from a vector)
normal3fSurface normal (role-based)
color3fRGB 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.

  • faceVertexCounts
  • faceVertexIndices
  • points

You do not need materials, lights, or xforms.

Visibility has only TWO valid values:

  • inherited (default): the prim inherits visibility from its parent
  • invisible: 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:

PurposeRendered?Use for
defaultAlwaysNormal geometry
renderRender passesHighest quality geometry
proxyViewportLightweight stand-in
guideNeverRig helpers, calculations only

Primvar interpolation modes: the number of required values differs by mode.

ModeValue countBehaviour
constant1Entire mesh gets one value
uniformNumber of facesOne value per face, no interpolation
vertexNumber of unique pointsPer vertex, surface-following interpolation
faceVaryingSum of all face vertex countsPer 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’s
  • strongerThanDescendants: 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):

  1. Session layer timeCodesPerSecond
  2. Root layer timeCodesPerSecond
  3. Session layer framesPerSecond
  4. Root layer framesPerSecond
  5. 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 Mesh cannot be the parent of another Mesh. Use an Xform as the parent.
  • Mark only Xforms as instanceable. If you make a Mesh instanceable, all instances stack at the same position.
  • All ancestors of a component kind prim must be group or assembly. Untyped prims in that chain break the model hierarchy chain.

11. Common Gotchas

  1. There is no "visible" visibility token. Use "inherited" to un-hide.
  2. Components cannot contain other components
  3. API schemas cannot inherit from other API schemas
  4. Only one typed schema per prim — multiple API schemas are fine
  5. Relationships do nothing without runtime code to interpret them
  6. Sublayers do not auto-correct orientation or scale. References and payloads do.
  7. Use codeless schemas when your pipeline has multiple DCCs on different USD versions
  8. Variant fallback only applies when no selection is authored at all
  9. GetProperties() ≠ GetAuthoredProperties(). The former includes schema defaults.
  10. Materials beat primvars: material colour wins over primvars:displayColor
  11. Time samples override default/local values completely
  12. extent is for bounding box calculations, not for rendering
  13. Inherits = broadcast (base class changes propagate). Specializes = OOP-like (derived keeps its own override).

More recent articles