Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The codebase follows the same module structure as the C++ OpenUSD SDK:
- `pcp/rel.rs` — `Relocates`: isolated relocate object owning per-layer `layerRelocates`. Resolves pre-relocation source paths, builds relocated prim indices, and adjusts child name lists. Each relocate node carries the source site's full layer stack (`Node::new_site` / `graft_relocate_node` take a `Vec<(usize, LayerOffset)>`), so a relocate source spanning several sublayers keeps every member. Receives external data (`&LayerStack`, cached indices/contexts) through method parameters; does not reference `Cache` directly.
- `pcp/clip.rs` — `ClipSet`: value clips (spec 12.3.4). Parses the explicit `clips` / `clipSets` metadata model and maps stage time to clip time during attribute value resolution. Template clips are resolved to explicit metadata elsewhere.

- **`usd/`** - Composed stage API: `usd::Stage` provides the high-level API for opening USD files and querying the composed scene graph. Delegates composition to `pcp::Cache`. `StageBuilder` configures the stage with a custom resolver, error handler (`on_error`), variant fallback selections (`variant_fallbacks`), an optional session layer (`session_layer`), payload loading behavior (`initial_load_set` / `InitialLoadSet`), and a working-set filter (`population_mask` / `StagePopulationMask`). `StageBuilder::open` loads a root layer from disk; `StageBuilder::in_memory` creates an empty writable anonymous root (C++ `UsdStage::CreateInMemory`). Stage-tier authoring (`define_prim` / `override_prim` / `create_attribute` / `create_relationship` / `set_default_prim`) routes through the current `EditTarget` (a subset of C++ `UsdEditTarget`): it pairs a `layer_index` with a `pcp::MapFunction` that maps scene paths to spec paths, so authoring through the chokepoint `Stage::with_target_layer_at` writes at `EditTarget::map_to_spec_path`. `EditTarget::for_layer_index` is the identity-mapping local target; `EditTarget::for_local_direct_variant` routes writes into a variant's `{set=sel}` namespace, which `sdf::Layer` materializes end-to-end (`create_prim` / `create_attribute` accept variant-segment paths and build the variant set / variant spec scaffolding via `namespace_chain`; arc-based reference/specialize targets are still TODO). `EditContext` is the RAII guard (C++ `UsdEditContext`) returned by `Stage::edit_context` that restores the previous target on `Drop`. Each authoring closure returns an `sdf::ChangeList` (in layer namespace) that `Cache::process_changes` classifies and applies for surgical invalidation. Errors surface via `StageAuthoringError` (including `OutsideEditTarget` when a path can't be mapped). `usd/prim.rs` defines `Prim` / `Attribute` / `Relationship` / `VariantSets` composed handles (C++ `UsdPrim` / `UsdAttribute` / `UsdRelationship` / `UsdVariantSets` analogs) — value-type wrappers around `(stage, path)` returned from the authoring methods. Setters consume `self` and return `Self` so chains bind directly (`let p = stage.define_prim(...)?.set_type_name(...)?.set_kind(...)?`); lookup-style methods (`create_attribute`, `path`, `get`, `time_samples`) take `&self`. Composition introspection mirrors the C++ handle surface: `Prim::prim_stack` (`UsdPrim::GetPrimStack`), `Attribute`/`Relationship::property_stack` (`UsdProperty::GetPropertyStack`), `Prim::variant_sets().get_all_variant_selections()` (`UsdVariantSets::GetAllVariantSelections`), plus the stage-scoped `Stage::layer_stack` (`UsdStage::GetLayerStack`) and `Stage::prim_at_path` (`UsdStage::GetPrimAtPath`); these return `(layer identifier, spec path)` sites where C++ returns spec handles. The older path-keyed `Stage` scene queries (`prim_children`, `relationship_targets`, …) are slated to move onto these handles (see the `TODO` in `usd/stage.rs`). Stage-level interpolation lives in `usd/interp.rs` and is exposed as `usd::InterpolationType`. Public users should import modules, e.g. `use openusd::{sdf, usd};`, rather than root-level `Stage` re-exports.
- **`usd/`** - Composed stage API: `usd::Stage` provides the high-level API for opening USD files and querying the composed scene graph. Delegates composition to `pcp::Cache`. `Stage` is a cheap reference-counted handle (`Rc<StageInner>`, mirroring C++ `UsdStageRefPtr`): cloning bumps the refcount, and all interior mutability lives in per-field cells on the crate-internal `StageInner`, reached through a `Deref`. `StageBuilder` configures the stage with a custom resolver, error handler (`on_error`), variant fallback selections (`variant_fallbacks`), an optional session layer (`session_layer`), payload loading behavior (`initial_load_set` / `InitialLoadSet`), and a working-set filter (`population_mask` / `StagePopulationMask`). `StageBuilder::open` loads a root layer from disk; `StageBuilder::in_memory` creates an empty writable anonymous root (C++ `UsdStage::CreateInMemory`). Stage-tier authoring (`define_prim` / `override_prim` / `create_attribute` / `create_relationship` / `set_default_prim`) routes through the current `EditTarget` (a subset of C++ `UsdEditTarget`): it pairs a `layer_index` with a `pcp::MapFunction` that maps scene paths to spec paths, so authoring through the chokepoint `Stage::with_target_layer_at` writes at `EditTarget::map_to_spec_path`. `EditTarget::for_layer_index` is the identity-mapping local target; `EditTarget::for_local_direct_variant` routes writes into a variant's `{set=sel}` namespace, which `sdf::Layer` materializes end-to-end (`create_prim` / `create_attribute` accept variant-segment paths and build the variant set / variant spec scaffolding via `namespace_chain`; arc-based reference/specialize targets are still TODO). `EditContext` is the RAII guard (C++ `UsdEditContext`) returned by `Stage::edit_context` that restores the previous target on `Drop`. Each authoring closure returns an `sdf::ChangeList` (in layer namespace) that `Cache::process_changes` classifies and applies for surgical invalidation. Errors surface via `StageAuthoringError` (including `OutsideEditTarget` when a path can't be mapped). `usd/prim.rs` defines `Prim` / `Attribute` / `Relationship` / `VariantSets` composed handles (C++ `UsdPrim` / `UsdAttribute` / `UsdRelationship` / `UsdVariantSets` analogs) — value-type wrappers around `(stage, path)` that own a `Stage` clone (no lifetime parameter), so they are `Clone` and can be stored independently of the call that produced them. `usd/schema.rs` defines `usd::SchemaBase` (C++ `UsdSchemaBase`), the root trait of the schema-view hierarchy: a view wraps a `Prim` and exposes `prim()` / `path()` / `stage()`, declares its `KIND` (`usd::SchemaKind`, mirroring `UsdSchemaKind`) for the `is_concrete` / `is_typed` / `is_*_api_schema` classification queries, and unwraps back to its `Prim` via `into_prim` (with a blanket `From<S: SchemaBase> for Prim`). The `schemas/` domain views are migrating onto it through intermediate schema traits; every schema family — `schemas::geom`, `schemas::lux`, `schemas::media`, `schemas::proc`, `schemas::vol`, `schemas::physics`, `schemas::ui`, `schemas::render`, `schemas::shade`, and `schemas::skel` — has fully moved (geom: a `SchemaBase → Imageable → Xformable → Boundable → Gprim → PointBased → Curves` trait chain with concrete newtype views like `Mesh` / `Camera` / `Sphere`; lux, media, proc, and vol build on it — lux lights, `media::SpatialAudio`, `proc::GenerativeProcedural`, and `vol::Volume` (a geom `Gprim`, with its field prims as geom `Xformable`) are geom prims, and `lux` / `media` / `proc` / `vol` enable the `geom` feature for that reuse; applied API schemas like `LightAPI` / `ShapingAPI` / `media::AssetPreviewsAPI` are single-apply views; `physics` stands apart from geom — typed `Scene` / `Joint` prims plus single- and multiple-apply API schemas, and the first family with multi-apply views (`DriveAPI` / `LimitAPI`, a `{prim, name}` newtype keyed by a DOF instance name, with `Get` / `GetAll`); `ui` is a small no-geom family — the typed `Backdrop` prim plus the single-apply `SceneGraphPrimAPI` / `NodeGraphNodeAPI`; `render` pairs the `RenderSettingsBase` interface trait (shared camera + framing attrs) with the typed `RenderSettings` / `RenderProduct` / `RenderVar` / `RenderPass` / `RenderDenoisePass`, and keeps the computed render spec (`compute_render_spec`, reading through the views); `shade` is connection-topology rather than flat attrs — the `Connectable` interface trait (`inputs:` / `outputs:` accessors plus `connectability` / `renderType` metadata and `Attribute::connect_to`, the general single-source connection helper) backs the typed `Shader` / `NodeGraph` / `Material`, the single-apply `MaterialBindingAPI` keeps the direct/collection binding-resolution machinery, and `Material::compute_surface_source` + `read_preview_surface` stay as connection-following computation; `UsdShadeInput`/`Output` are exposed as raw `Attribute` handles rather than distinct value types; `skel` is the only geom-derived family besides geom itself with a kept computation toolkit — `SkelRoot` / `Skeleton` are geom `Boundable` prims, `SkelAnimation` / `BlendShape` are typed, `SkelBindingAPI` is single-apply, and the time-independent object model (`Topology`, `AnimMapper`, `SkeletonResolver`, `SkinningResolver`, `SkelAnimQuery`, `discover_bindings`, the LBS / blend-shape math) is rewired to build from the views via decoded getters like `Skeleton::joints()` / `bind_transforms()`). All ten replace the old freestanding `read_*` / `*Author` functions; each concrete view declares its chain via an `impl_<family>_schema!` macro and exposes C++-style `foo_attr()` / `create_foo_attr()` handle pairs. A migrated family is laid out as `mod.rs` (re-exports + the macro + token-valued enums), `tokens.rs`, `schema.rs` (the view structs, grouped — geom splits these by sub-family), and an optional `traits.rs` (the interface traits); the shared view-gate helpers (`get_typed` / `get_typed_any` / `get_with_api`) and the `impl_token_value!` macro (gives a token enum `From`/`TryFrom<Value>` for direct `set(Enum)` / `get::<Enum>()`) live in `schemas::common`, while attribute authoring is inlined via the `Attribute` builder (`create_attribute(name, type).set_custom(false)`). The registry-backed members (`GetSchemaClassPrimDefinition`, `GetSchemaAttributeNames`) await the `schemas::registry`. Setters consume `self` and return `Self` so chains bind directly (`let p = stage.define_prim(...)?.set_type_name(...)?.set_kind(...)?`); lookup-style methods (`create_attribute`, `path`, `get`, `time_samples`) take `&self`. Composition introspection mirrors the C++ handle surface: `Prim::prim_stack` (`UsdPrim::GetPrimStack`), `Attribute`/`Relationship::property_stack` (`UsdProperty::GetPropertyStack`), `Prim::variant_sets().get_all_variant_selections()` (`UsdVariantSets::GetAllVariantSelections`), plus the stage-scoped `Stage::layer_stack` (`UsdStage::GetLayerStack`) and `Stage::prim_at_path` (`UsdStage::GetPrimAtPath`); these return `(layer identifier, spec path)` sites where C++ returns spec handles. The older path-keyed `Stage` scene queries (`prim_children`, `relationship_targets`, …) are slated to move onto these handles (see the `TODO` in `usd/stage.rs`). Stage-level interpolation lives in `usd/interp.rs` and is exposed as `usd::InterpolationType`. Public users should import modules, e.g. `use openusd::{sdf, usd};`, rather than root-level `Stage` re-exports.

- **`expr`** - Variable expression tokenizer and parser for USD's expression syntax.

Expand Down Expand Up @@ -94,6 +94,7 @@ When implementing a new feature from the spec:
- 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
- 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"). 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`
Expand Down
21 changes: 16 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,25 @@ keywords = ["usd", "usda", "usdc", "usdz", "pxr"]

[features]
geom = []
lux = []
media = []
# UsdLux lights are UsdGeom Xformable / Boundable prims, so the lux views
# build on the geom schema traits.
lux = ["geom"]
# UsdMediaSpatialAudio is a UsdGeomXformable-derived prim, so the media views
# build on the geom schema traits.
media = ["geom"]
physics = []
proc = []
# UsdProcGenerativeProcedural is a UsdGeomBoundable-derived prim, so the proc
# views build on the geom schema traits.
proc = ["geom"]
render = []
shade = []
skel = []
# UsdSkelRoot and UsdSkelSkeleton are UsdGeomBoundable-derived prims, so the
# skel views build on the geom schema traits.
skel = ["geom"]
ui = []
vol = []
# UsdVolVolume is a UsdGeomGprim and the field prims are UsdGeomXformable, so
# the vol views build on the geom schema traits.
vol = ["geom"]
serde = ["dep:serde", "half/serde"]

[dependencies]
Expand All @@ -34,6 +44,7 @@ half = { version = "2.3.1", features = ["bytemuck", "num-traits"] }
logos = "0.16.0"
serde = { version = "1", features = ["derive"], optional = true }
zip = { version = "8.2.0", default-features = false, features = ["deflate"] }
derive_more = { version = "2.1.1", features = ["deref"] }

[dev-dependencies]
diff_json = "0.1"
Expand Down
40 changes: 23 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ openusd = { version = "0.4", features = ["geom", "lux"] }
## Example

```rust,no_run
use openusd::{ar, sdf, usd};
use openusd::{ar, usd};

// Open a stage with default settings (DefaultResolver, strict errors, all payloads loaded).
let stage = usd::Stage::open("scene.usda")?;
Expand Down Expand Up @@ -143,26 +143,32 @@ let active = stage.is_active("/World/Hero")?;
let is_model = stage.is_model("/World/Hero")?;
let type_name = stage.type_name("/World/Hero")?;

// Read a typed field value.
let visible: Option<bool> = stage.field("/World/Hero", sdf::FieldKey::Active)?;

// Access children composed across layers, references, and payloads.
let children = stage.prim_children("/World/Hero")?;
let properties = stage.prim_properties("/World/Hero")?;
```

With the `geom` feature, read typed schema views over the composed stage — here a
`Mesh` and its point positions and normals:

```rust,no_run
// `PointBased` is brought in so its inherited accessors resolve on the view.
use openusd::schemas::geom::{self, PointBased};
use openusd::usd;

let stage = usd::Stage::open("scene.usda")?;

// Author into an in-memory stage.
let stage = usd::Stage::builder().in_memory("anon.usda")?;
let mesh = stage
.define_prim("/World/Mesh")?
.set_type_name("Mesh")?
.set_kind("component")?;
let radius = mesh
.create_attribute("radius", "double")?
.set_variability(sdf::Variability::Uniform)?
.set(sdf::Value::Double(1.0))?;
let binding = mesh
.create_relationship("material:binding")?
.add_target(sdf::Path::new("/World/Material")?)?;
if let Some(mesh) = geom::Mesh::get(&stage, "/World/Mesh")? {
// `points_attr` / `normals_attr` are inherited from the `PointBased` trait
// up the chain. `point3f[]` and `normal3f[]` both decode to `Vec<[f32; 3]>`,
// so `get` extracts them directly.
let points = mesh.points_attr().get::<Vec<[f32; 3]>>()?;
let normals = mesh.normals_attr().get::<Vec<[f32; 3]>>()?;

if let Some(points) = points {
println!("{} points, normals authored: {}", points.len(), normals.is_some());
}
}
```

More runnable examples live in the [`examples/`](examples) directory:
Expand Down
Loading
Loading