Skip to content

Latest commit

 

History

History
162 lines (117 loc) · 14.8 KB

File metadata and controls

162 lines (117 loc) · 14.8 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

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.

Architecture

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, chiefly tf::Token, the interned-identifier string behind every TfToken-equivalent API. Start at tf/mod.rs.

  • sdf/ - Scene Description Foundations (C++ Sdf): the core data model. AbstractData is the unified read/write backend interface the text, binary, and archive readers all implement; Value, Path, and Layer (C++ SdfLayer) are the everyday types. A Layer's edits run through Layer::edit (a closure over a LayerEdit view) as atomic copy-on-write transactions that derive a ChangeList for composition invalidation and fire per-layer LayerSinks at the commit seam (sdf/layer.rs, sdf/change.rs, sdf/sink.rs); spec authoring lives on the typed views (PrimSpec::new and friends). Also here: the pluggable FileFormat read/write seam, the LayerRegistry that 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 at sdf/mod.rs.

  • usda/ - Text format .usda: logos lexer + recursive-descent parser. TextReader / TextWriter.

  • usdc/ - Binary crate format .usdc (compressed). CrateData / CrateWriter.

  • usdz/ - ZIP-based .usdz package. Archive / ArchiveWriter.

  • ar/ - Asset Resolution (C++ Ar): the Resolver trait maps @...@ asset paths to physical locations; DefaultResolver searches 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 through BuildOutput to the stage's load barrier. Start at pcp/mod.rs — it has the LIVERPS overview and a per-file structure table.

  • usd/ - Composed stage API (C++ Usd): usd::Stage is the handle that delegates composition to pcp::IndexCache; Prim, Attribute, Relationship, and the schema views are Clone value types over it, and stage-tier authoring routes through the current EditTarget, with Stage::batch_edit for atomic multi-layer edits. Notable sub-surfaces: usd/sink.rs (StageSink composed-change observers, Provenance), usd/diff.rs (the transferable Diff and Stage::apply_diff), usd/capture.rs (UndoStage / ReplayStage, recording wrappers over the change seam), usd/editor.rs (namespace editing). Start at usd/stage.rs. Public users import modules (use openusd::{sdf, usd};), not root-level re-exports.

  • schemas/ - Domain schemas layered on sdf / usd, not part of the AOUSD core spec. Feature-gated per family (geom, lux, media, physics, proc, render, shade, skel, ui, vol; some enable geom transitively). See the table in schemas/mod.rs.

  • gf/ - Graphics Foundations (C++ Gf): bytemuck::Pod vector / quaternion / matrix types for bulk binary serialization, row-major / row-vector convention, each bridging to sdf::Value via From / TryFrom. See gf/mod.rs for the conventions.

Development Commands

# 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.usd

ROADMAP.md Style

Keep ROADMAP.md rows concise. Each Notes cell should cover three things only:

  1. Whether it is done — the status emoji in the Status column handles this; the Notes cell does not need to restate it.
  2. 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.
  3. What is remaining — a short Remaining — X; Y clause, or a Remaining: 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.

Planning New Features

When implementing a new feature from the spec:

  1. Read docs/aousd_core_spec_1.0.1.pdf to understand how the feature works and what it does
  2. Research how the C++ OpenUSD implementation handles it: https://github.com/PixarAnimationStudios/OpenUSD
  3. Review the Python reference implementation if applicable: vendor/core-spec-supplemental-release_dec2025/

Code Standards

  • Project targets Rust version specified in rust-toolchain.toml with MSRV defined in clippy.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 format branch, 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 a TODO naming the missing generalization.

Code Quality

  • 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 pcp composition free of interior mutability: no Cell/RefCell (and no Mutex) on LayerGraph, 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 plain Vec it 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 impl blocks, 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** — description pattern 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 a Relocate type 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 prefix subtree") 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) (or TODO(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::FieldKey not sdf::schema::FieldKey)
  • Reference types through their module, not a fully-qualified or bare path: use crate::{sdf, gf, tf}; then write sdf::TimeCode, gf::Vec3f, tf::Token. Don't write inline crate::tf::Token::from(...) (or openusd::tf::Token in tests), and don't use crate::tf::Token; to get a bare Token when tf::Token reads clearly (it also avoids clashes, e.g. the usda lexer's own Token)
  • NEVER write a fully-qualified std path inline (e.g. std::collections::HashMap::new(), std::cell::RefCell::new(...), std::mem::swap(...)). ALWAYS add a use at the top of the file and reference the short name (HashMap::new(), RefCell::new(...), mem::swap(...) after use std::mem). This applies to every std:: (and external-crate) path: import it, then use the short form
  • Avoid raw path string manipulations; use Path methods instead of building or parsing path strings manually
  • Mutate a Cell / RefCell through its own methods, not a deref-assignment: prefer cell.replace(v), cell.replace_with(|old| …), cell.take(), and (for Cell) cell.set(v) / cell.get() over *cell.borrow_mut() = v or *cell.borrow_mut() += …; when you hold &mut self, reach the value with cell.get_mut() to skip the runtime borrow entirely. Use a Cell (not a RefCell) for a Copy field like a counter or flag
  • Don't add "Generated with Claude Code" or "Co-Authored-By: Claude" to commits, PRs, or release notes

Testing

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.

Dependencies

Key external dependencies:

  • anyhow - Error handling
  • bitflags - Bitflag sets (e.g. PrimPredicate)
  • bytemuck - Safe transmutation for binary data
  • half - 16-bit floating point support (re-exported as f16)
  • logos - Lexer generator for USDA tokenization
  • lz4_flex - Compression for binary format
  • num-traits - Numeric traits
  • regex-lite - Lightweight regex engine for the matches_regex expression function
  • strum - Enum derive macros (Display, EnumIs, EnumTryAs, IntoStaticStr, etc.)
  • thiserror - Error type derive macros for typed errors such as layer::Error and pcp::Error
  • zip - USDZ archive reading
  • serde (optional, serde feature) - 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.