This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a pure Rust implementation of OpenUSD (Universal Scene Description), Pixar's open-source framework for 3D computer graphics data interchange. The project aims to provide native Rust support without C++ dependencies for reading and writing USD files.
The codebase mirrors the C++ OpenUSD SDK's module layout. The bullets below are only a navigational map — what each module is and where to start reading. They deliberately do not enumerate types or methods: the project is large, and any inventory kept here would drift out of date. Each module's own //! doc comment, and each item's own doc comment, are the source of truth — read those for specifics rather than expecting them here.
-
tf/- Tools Foundation (C++Tf): low-level utilities, chieflytf::Token, the interned-identifier string behind everyTfToken-equivalent API. Start attf/mod.rs. -
sdf/- Scene Description Foundations (C++Sdf): the core data model.AbstractDatais the unified read/write backend interface the text, binary, and archive readers all implement;Value,Path, andLayer(C++SdfLayer) are the everyday types. ALayer's edits run throughLayer::edit(a closure over aLayerEditview) as atomic copy-on-write transactions that derive aChangeListfor composition invalidation and fire per-layerLayerSinks at the commit seam (sdf/layer.rs,sdf/change.rs,sdf/sink.rs); spec authoring lives on the typed views (PrimSpec::newand friends). Also here: the pluggableFileFormatread/write seam, theLayerRegistrythat owns asset loading and format dispatch (composition opens reference/payload targets on demand through it, so an un-visited subtree never loads), the variable-expression engine (sdf/expr.rs), and the namespace-aware spec-copy primitive (sdf/copy.rs). Start atsdf/mod.rs. -
usda/- Text format.usda: logos lexer + recursive-descent parser.TextReader/TextWriter. -
usdc/- Binary crate format.usdc(compressed).CrateData/CrateWriter. -
usdz/- ZIP-based.usdzpackage.Archive/ArchiveWriter. -
ar/- Asset Resolution (C++Ar): theResolvertrait maps@...@asset paths to physical locations;DefaultResolversearches the filesystem. -
pcp/- Prim Cache Population, the composition engine (C++Pcp): LIVERPS strength ordering across layers, kept a pure function of(graph, context, cached indices)so it stays parallelizable. Composition drives layer loading: a reference/payload target opens on first use and the demand travels out throughBuildOutputto the stage's load barrier. Start atpcp/mod.rs— it has the LIVERPS overview and a per-file structure table. -
usd/- Composed stage API (C++Usd):usd::Stageis the handle that delegates composition topcp::IndexCache;Prim,Attribute,Relationship, and the schema views areClonevalue types over it, and stage-tier authoring routes through the currentEditTarget, withStage::batch_editfor atomic multi-layer edits. Notable sub-surfaces:usd/sink.rs(StageSinkcomposed-change observers,Provenance),usd/diff.rs(the transferableDiffandStage::apply_diff),usd/capture.rs(UndoStage/ReplayStage, recording wrappers over the change seam),usd/editor.rs(namespace editing). Start atusd/stage.rs. Public users import modules (use openusd::{sdf, usd};), not root-level re-exports. -
schemas/- Domain schemas layered onsdf/usd, not part of the AOUSD core spec. Feature-gated per family (geom,lux,media,physics,proc,render,shade,skel,ui,vol; some enablegeomtransitively). See the table inschemas/mod.rs. -
gf/- Graphics Foundations (C++Gf):bytemuck::Podvector / quaternion / matrix types for bulk binary serialization, row-major / row-vector convention, each bridging tosdf::ValueviaFrom/TryFrom. Seegf/mod.rsfor the conventions.
# Build the project (use --all-features to include the gated schema modules)
cargo build --all-features
# Run tests (including comprehensive format validation tests)
cargo test --all-targets --all-features
# Lint with Clippy (strict warnings as errors)
cargo clippy --all-targets --all-features -- -D warnings
# Format code
cargo fmt
# Check formatting
cargo fmt --all -- --check --files-with-diff
# Generate documentation
cargo doc --no-deps
# Run security/dependency audits
cargo deny check
# Run examples
cargo run --example dump_usdc -- path/to/file.usdKeep ROADMAP.md rows concise. Each Notes cell should cover three things only:
- Whether it is done — the status emoji in the Status column handles this; the Notes cell does not need to restate it.
- What to read — name the key types or traits a reader would look for
(e.g.
sdf::ChangeList,usd::NamespaceEditor). Do not describe what those types do; their own doc comments are the source of truth. - What is remaining — a short
Remaining — X; Yclause, or aRemaining:bullet list when there are several distinct items, for anything materially incomplete. Drop it once nothing is left.
Avoid enumerating every method, variant, or edge case handled. If the implementation is complete and unremarkable, a one-line entry is fine.
When implementing a new feature from the spec:
- Read
docs/aousd_core_spec_1.0.1.pdfto understand how the feature works and what it does - Research how the C++ OpenUSD implementation handles it: https://github.com/PixarAnimationStudios/OpenUSD
- Review the Python reference implementation if applicable:
vendor/core-spec-supplemental-release_dec2025/
- Project targets Rust version specified in
rust-toolchain.tomlwith MSRV defined inclippy.toml - Maximum line width: 120 characters (rustfmt.toml)
- All warnings treated as errors in CI
- Comprehensive test coverage (50% minimum) with grcov
- Security auditing with cargo-deny
- Pre-1.0: backward compatibility is not a constraint. Prefer the cleanest design and change or remove public APIs freely; don't keep deprecated shims, compatibility shims, or worse-but-compatible behavior. Update all call sites in the same change.
- Prefer an architecturally correct design over a quick, dirty, or bandaid fix.
Fix the root cause at the layer that owns the concept, not the symptom at the
call site. A special case layered onto shared infrastructure (an
if this one formatbranch, or a value re-derived from a string because the right field was not carried) signals the fix is at the wrong altitude — generalize the mechanism or carry the missing state instead. When a module mirrors C++ OpenUSD, factor the concern the way the C++ model does (e.g. a layer's lexical identifier vs. its resolved real path). Note genuinely deferred depth as aTODOnaming the missing generalization.
- Write clean and idiomatic Rust code
- Less is better - prefer functionality offered by stdlib
- Don't write a function or method whose body is just a call to another free-standing function (a thin delegating wrapper, e.g.
fn open_stack(&self, p) { open_layer_stack(self.resolver(), p) }). Call the free function directly at the call site. A method earns its place only when it adds something — encapsulating private state, a non-trivial default, or an invariant - Keep
pcpcomposition free of interior mutability: noCell/RefCell(and noMutex) onLayerGraph,IndexCache, the indexer, or anything a per-prim build reads. A build is a pure function of its&-inputs, so anything it discovers (errors, demanded-but-unloaded layers) travels out through its return value (BuildOutput), not a shared mutable cell. Accumulate such results in an owned field and merge them up; the cache holds the deferred list as a plainVecit mutates through&mut self - Order a file top-down by importance so the first thing a reader sees is the main type, not a helper: the primary type definition (and the structs it depends on) first, then its
implblocks, then free-standing helper functions, then the#[cfg(test)] mod tests. Don't open a file with a small private helper. - Code requires documentation
- Proof read and reword docs and/or comments as needed
- Do not use
**bold** — descriptionpattern in doc comments or bullet lists; use plain text or link directly to the item instead - A doc comment documents only its own item. Don't describe another type, module, or method inside it (e.g. don't enumerate a
Layer's methods in aRelocatetype alias's doc); document each item on the item itself and use an intra-doc link ([`Foo`]) when a cross-reference is genuinely needed - Do not use decorative box-drawing section-divider comments (e.g.
// ── Section ──────); group code with a plain//comment or rely on the item's own doc comment - Never remove comments during refactoring if they are still applicable
- Comments must describe the code as it stands, not its edit history or the alternatives it didn't take. Don't justify the absence or removal of code, and don't contrast the chosen approach with a rejected one (e.g. "no separate X pre-check is needed here", "X was removed because…", "assign directly rather than through Y", "instead of calling Z", "we use A so we don't B"). This includes contrasts with a prior implementation's performance or shape — "a subtree walk rather than a full scan", "O(1) instead of the previous O(n)", "now keyed by path" — which only make sense to someone who saw the old code; state the present behavior positively ("walks the
prefixsubtree") instead. Such notes only make sense to someone who saw the prior version or the alternative and are noise to a fresh reader. State what the present code does and a rationale that stands on its own, not what it no longer does or could have done. - Don't reference planning phases or steps (e.g. "Phase 1", "Step 2") in code, comments, names, fixtures, or commit messages; describe what the code does or, for deferred work, name the missing feature in a
TODO - Wrap prose at 80 characters — Markdown, plans, design write-ups, and doc-comment text; Rust code still follows rustfmt (120)
- Mark performance/parallelism opportunities with a
TODO(rayon)(orTODO(perf)) comment in new code and when refactoring existing code, instead of optimizing prematurely; say what is independent or parallelizable so the seam is actionable later - Re-export key types from module roots so users can access them without deep paths (e.g.
sdf::FieldKeynotsdf::schema::FieldKey) - Reference types through their module, not a fully-qualified or bare path:
use crate::{sdf, gf, tf};then writesdf::TimeCode,gf::Vec3f,tf::Token. Don't write inlinecrate::tf::Token::from(...)(oropenusd::tf::Tokenin tests), and don'tuse crate::tf::Token;to get a bareTokenwhentf::Tokenreads clearly (it also avoids clashes, e.g. theusdalexer's ownToken) - NEVER write a fully-qualified
stdpath inline (e.g.std::collections::HashMap::new(),std::cell::RefCell::new(...),std::mem::swap(...)). ALWAYS add auseat the top of the file and reference the short name (HashMap::new(),RefCell::new(...),mem::swap(...)afteruse std::mem). This applies to everystd::(and external-crate) path: import it, then use the short form - Avoid raw path string manipulations; use
Pathmethods instead of building or parsing path strings manually - Mutate a
Cell/RefCellthrough its own methods, not a deref-assignment: prefercell.replace(v),cell.replace_with(|old| …),cell.take(), and (forCell)cell.set(v)/cell.get()over*cell.borrow_mut() = vor*cell.borrow_mut() += …; when you hold&mut self, reach the value withcell.get_mut()to skip the runtime borrow entirely. Use aCell(not aRefCell) for aCopyfield like a counter or flag - Don't add "Generated with Claude Code" or "Co-Authored-By: Claude" to commits, PRs, or release notes
The test suite includes extensive binary format tests using fixture files in fixtures/ directory. Tests validate:
- Data type parsing (integers, floats, strings, arrays, etc.)
- USD-specific types (paths, references, payloads, layer offsets)
- Compression handling
- Time-sampled data
- Scene hierarchy traversal
Prefer using USD assets from vendor/usd-wg-assets/ for test fixtures when a suitable file exists. Only add new files to fixtures/ when vendor assets don't cover the specific case needed.
Never put a test-only function in a module's main body — not even gated with #[cfg(test)]. Every test helper lives inside a #[cfg(test)] mod tests { … } block. A helper that is intrinsically about a production type and is shared across modules' tests goes as a #[cfg(test)] method in a #[cfg(test)] impl block on that type (e.g. LayerRegistry::collect_with_arcs), reached as Type::helper(...). Don't add a separate test_support module for it.
Test function names MUST be terse — 2–4 underscore-separated words, no more. Match the existing naming convention of the file. Prefer add_api_schema_dup_delete over add_api_schema_clears_duplicate_delete_opinions, light_api_skips_non_light over read_light_api_returns_none_on_non_light_prim. Drop redundant prefixes like read_/reads_ when the test file already targets a reader; favour the subject + outcome (light_api_via_applied_schema) over verbose sentences.
To pull a typed payload out of a Value in tests, use the EnumTryAs-generated try_as_*() accessor followed by unwrap()/expect("…") rather than a let Value::Variant(x) = … else { panic!() } block. Keep the descriptive message by preferring expect.
Key external dependencies:
anyhow- Error handlingbitflags- Bitflag sets (e.g.PrimPredicate)bytemuck- Safe transmutation for binary datahalf- 16-bit floating point support (re-exported asf16)logos- Lexer generator for USDA tokenizationlz4_flex- Compression for binary formatnum-traits- Numeric traitsregex-lite- Lightweight regex engine for thematches_regexexpression functionstrum- Enum derive macros (Display, EnumIs, EnumTryAs, IntoStaticStr, etc.)thiserror- Error type derive macros for typed errors such aslayer::Errorandpcp::Errorzip- USDZ archive readingserde(optional,serdefeature) - Serialization support
Domain schemas are gated behind per-module features (geom, lux, media, physics, proc, render, shade, skel, ui, vol); use --all-features when building, testing, or linting.
The project maintains a minimal dependency footprint and uses cargo-deny to prevent license conflicts and vulnerability introduction. Allowed licenses: MIT, Apache-2.0, Zlib, Unicode-3.0.