2026-06-01
Hand-writing OpenUSD Python from the docs
openusdpythonlearning-lognetwork-engineer-bridge
The previous post covered the scan-import-render pipeline. This one is about authoring USD by hand in Python so the abstractions stop being magic.
The shift
Day 1 was operating on USDZ (scan it, import it, render it). Day 2 is authoring USD by creating a stage from scratch in Python, defining prims one at a time, reading the .usda text format, and watching it change as I mutate the in-memory scenegraph.
This is the part where the OpenUSD bridge to network-engineering mental models starts paying off. The text format reads like a YANG-shaped tree. The Python API reads like NSO’s MAAPI. The composition arcs (next session) will read like NSO services + YANG augments. But you don’t see any of that until you’ve handwritten the API a few times. Working through LearnOpenUSD: Setting the Stage (seven pages, around 90 minutes if you read carefully and run every snippet).
What clicked
The moment OpenUSD stopped feeling like a mystery was the moment I opened a saved .usda file in a text editor for the first time. I’d assumed USD was some opaque binary format you only interacted with through tooling. It’s not. The native authoring format is human-readable text, the syntax is a hierarchical tree of typed prims with attributes, and the whole thing reads like a YANG document that happens to describe geometry instead of network config. Once I saw the text, the Python API made sense. The API is just a procedural way to author the same tree I could have typed by hand.
The other thing that clicked: there’s no substitute for typing the API out a few times. I’d spent the previous week watching Omniverse YouTube content and reading documentation, which gave me vocabulary but not muscle memory. Writing stage = Usd.Stage.CreateNew(path) and watching the .usda file appear on disk produced more understanding in 20 minutes than the previous week had.
Where the network-engineer mental model genuinely accelerates the first day of OpenUSD work:
- Source of truth lives in the model.
- You compose opinions rather than overwriting them.
- The text format is what you diff and version-control.
These all map cleanly onto how NSO and YANG work. I wasn’t learning new architecture, I was learning a new vocabulary for an architecture I already had.
Where it stops accelerating: USD’s geometry-specific schemas (Xform, Mesh, Material, UsdShade, UsdLux) have no NSO analog. USD’s time-sampled attribute values have no YANG analog. USD’s Hydra rendering layer is a whole separate concern downstream of the authoring model. Knowing where the bridge stops is as useful as knowing where it starts. It tells me where to budget the learning hours.
Stage lifecycle: three constructors, one save
Usd.Stage.CreateNew(path) # new file, errors if exists, writes empty .usda immediately
Usd.Stage.CreateInMemory() # scratch stage, no file at all
Usd.Stage.Open(path) # load existing into memory
stage.Save() # flush in-memory mutations to disk
Mirrors Python’s open(path, 'w') / open(path, 'r') lifecycle. The thing I missed for a few minutes: mutations are in-memory only until you call .Save(). CreateNew writes an empty file at creation time, but anything you DefinePrim afterward sits in memory until saved.
Network analogy: CreateNew ≈ initialize and commit an empty candidate config; Open ≈ pull running-config into a candidate; Save ≈ commit after edits.
DefinePrim(path, type): one thing’s arbitrary, one isn’t
stage.DefinePrim("/World/Chair", "Cube")
# ^ ^
# path (yours) type (USD's)
The path is whatever you want. The type has to be a schema USD knows (Xform, Cube, Sphere, Mesh, Material). Pass an unknown type string and the prim survives but has no schema methods attached. Auto-create behavior: DefinePrim("/A/B/C", "Cube") silently creates A and B as typeless prims if they don’t exist. Convenient, but easy to miss.
Network analogy: path is the XPath in a YANG datastore (/interfaces/interface[name='Eth1/1']), author-chosen. Type is the YANG container or leaf type definition, drawn from a fixed registered model. You can’t def Banana for the same reason you can’t have a YANG leaf of type banana without first defining the typedef.
Python type annotations aren’t enforcement
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
The : Usd.Stage is decoration. Documentation for IDEs and mypy, ignored by the runtime. You can delete it and the program runs identically. You can also lie (stage: int = ...) and it still runs. One of those Python gotchas that hits everyone from a typed-language background.
What .usda text actually looks like
After authoring a few prims by hand and saving:
#usda 1.0
def Xform "World"
{
def Cube "Chair"
{
}
}
That’s it. def keyword, type, name, body. Hierarchy is nested braces. Every line maps 1:1 to a Python API call. The text format is the canonical wire-format of USD: what gets diffed, version-controlled, and code-reviewed. This is the part that maps cleanest onto YANG: declarative, hierarchical, schema-typed, plain-text-canonicalized.
Friction notes
OpenUSD’s Python API breaks PEP 8. Methods are CapitalCase, not snake_case. Standard Python would write stage.create_new(...). OpenUSD writes Stage.CreateNew(...) because the Python bindings preserve the C++ API’s naming. Every Python tutorial outside the Pixar and NVIDIA ecosystem trains the opposite convention. Doc-fix: the “Setting the Stage” intro could call this out: “Note for Python developers: USD’s Python bindings preserve the C++ API’s CapitalCase naming. This is intentional but breaks PEP 8 expectations.”
Usd.Stage.CreateNew is exclusive-create and silent on rerun. Iterating on the hello-world script fails the second run because the file already exists. Fix is rm between runs, switch to Open, or use CreateInMemory for iteration loops. Doc-fix: teach the three-call cheatsheet upfront in the lesson, not buried in the API reference. Right now CreateNew is taught first as if it’s the normal entry point. For an iteration workflow it’s the wrong one.
The cross-domain connection (still load-bearing)
The day-1 analogies stack continues to hold:
- OpenUSD Stage ↔ NSO CDB: composed source of truth.
- DefinePrim path/type split ↔ XPath + YANG typedef: author-chosen path, registered-schema type.
.usdatext format ↔ YANG declarative tree: hierarchical, plain-text-canonicalized, diffable.Save()semantics ↔ NSOcommit: until then, mutations live in candidate only.
The mental-model accelerator runs for the first ~80% of the surface area, then you need to re-tool for the parts that are genuinely USD-specific.
What’s next
Tomorrow: Composition Basics (Layers → References → Strength Ordering), then redo 01_references.py by hand. That’s the chapter where the “single source of truth” / “non-destructive pipeline” terminology I keep hearing from NVIDIA’s Physical AI talks finally becomes operational.
Reach me on LinkedIn or via Sierra Code Co.