Skip to content

feat(stage): add Stage::up_axis() -> Option<UpAxis>#42

Closed
yohawing wants to merge 1 commit intomxpv:mainfrom
yohawing:upstream-review/stage-up-axis
Closed

feat(stage): add Stage::up_axis() -> Option<UpAxis>#42
yohawing wants to merge 1 commit intomxpv:mainfrom
yohawing:upstream-review/stage-up-axis

Conversation

@yohawing
Copy link
Copy Markdown
Contributor

@yohawing yohawing commented Apr 7, 2026

Summary

Adds a typed accessor for the root layer's upAxis metadata, which is the first piece of scene-convention metadata most downstream renderers / viewers need:

pub fn up_axis(&self) -> Option<UpAxis>

where UpAxis is a small Y/Z enum. USD defines only "Y" and "Z" as valid values; any other authored string is treated as None so callers can continue inspecting the rest of the stage without erroring on malformed data.

Changes

  • src/sdf/schema.rs: add FieldKey::UpAxis ("upAxis").
  • src/stage.rs: add pub enum UpAxis { Y, Z } and Stage::up_axis().
  • fixtures/up_axis_y.usda, fixtures/up_axis_z.usda, fixtures/up_axis_absent.usda: minimal fixtures.
  • Tests added in stage::tests:
    • up_axis_y: Y-up fixture returns Some(UpAxis::Y)
    • up_axis_z: Z-up fixture returns Some(UpAxis::Z)
    • up_axis_absent_returns_none: no-metadata fixture returns None

Rationale

The common alternative is to expose upAxis as a raw string via the existing field::<String> API, but:

  1. Callers consistently want a typed enum (every downstream renderer writes the same 3-line match).
  2. Malformed values should not crash callers doing asset inspection.

A companion PR for Stage::meters_per_unit() is coming separately so that each accessor can be reviewed independently.

Test plan

  • cargo test passes (228 tests, 3 new)
  • cargo clippy --all-targets -- -D warnings clean
  • cargo fmt --check clean

Adds a typed accessor for the root layer's upAxis metadata:

    pub fn up_axis(&self) -> Option<UpAxis>

where UpAxis is a small Y/Z enum.  USD defines only "Y" and "Z"
as valid values; any other authored string is treated as None so
that callers can continue inspecting the rest of the stage without
erroring on malformed data.

Changes:
- Add FieldKey::UpAxis ("upAxis") to sdf::schema.
- Add pub enum UpAxis { Y, Z } to stage.rs.
- Add Stage::up_axis() accessor.

Tests added:
- up_axis_y: Y-up fixture returns Some(UpAxis::Y).
- up_axis_z: Z-up fixture returns Some(UpAxis::Z).
- up_axis_absent_returns_none: fixture without upAxis returns None.

Fixtures added:
- fixtures/up_axis_y.usda
- fixtures/up_axis_z.usda
- fixtures/up_axis_absent.usda
Copilot AI review requested due to automatic review settings April 7, 2026 07:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a typed accessor on Stage to read the composed stage up-axis convention from authored upAxis metadata, plus fixtures and tests to validate Y/Z and missing metadata behavior.

Changes:

  • Introduces UpAxis enum and Stage::up_axis() -> Option<UpAxis> accessor.
  • Registers FieldKey::UpAxis ("upAxis") in the Sdf schema field key list.
  • Adds minimal .usda fixtures and tests for Y-up, Z-up, and absent upAxis.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/stage.rs Adds UpAxis enum, Stage::up_axis() implementation, and unit tests.
src/sdf/schema.rs Adds FieldKey::UpAxis and maps it to "upAxis".
fixtures/up_axis_y.usda Fixture with upAxis = "Y" metadata.
fixtures/up_axis_z.usda Fixture with upAxis = "Z" metadata.
fixtures/up_axis_absent.usda Fixture without upAxis metadata.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +102 to +106
/// Returns the `upAxis` metadata from the root layer, if set.
///
/// USD defines two valid values: `"Y"` and `"Z"`. Any other authored
/// value (malformed data) is treated as `None` rather than an error so
/// that callers can continue inspecting the rest of the stage.
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says this returns upAxis metadata "from the root layer", but the implementation uses self.field(...) which resolves the strongest opinion across the entire layer stack (it may come from a sublayer if the strongest/root layer doesn’t author it). Consider clarifying the wording (e.g., “from the pseudo-root (composed across layers)” or “from the root layer stack”) to match actual behavior.

Copilot uses AI. Check for mistakes.
/// }
/// ```
pub fn up_axis(&self) -> Option<UpAxis> {
let raw = self.field::<String>(&Path::abs_root(), FieldKey::UpAxis).ok()??;
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok()?? works here but is a bit hard to read at a glance (it’s relying on Option-? twice to drop both Err and None). Consider rewriting this in a more explicit/idiomatic way (e.g. using .ok().flatten()? or a small match) to improve maintainability.

Suggested change
let raw = self.field::<String>(&Path::abs_root(), FieldKey::UpAxis).ok()??;
let raw = self
.field::<String>(&Path::abs_root(), FieldKey::UpAxis)
.ok()
.flatten()?;

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +128
match raw.as_str() {
"Y" => Some(UpAxis::Y),
"Z" => Some(UpAxis::Z),
_ => None,
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function’s behavior for malformed authored values (anything other than "Y"/"Z" => None) is part of the stated contract, but there isn’t a test covering that case. Adding a small fixture (e.g. upAxis = "X") and a corresponding test would help prevent regressions.

Copilot uses AI. Check for mistakes.
@yohawing
Copy link
Copy Markdown
Contributor Author

yohawing commented Apr 7, 2026

Hi @mxpv,

Apologies — this PR (and the other four in this batch: #41 #42 #43 #44 #45) was opened by an AI agent on my behalf without enough human review of upstream etiquette. I should have filed an issue first to discuss before dropping a stack of code on you. Sorry for the noise.

For context: I'm building yw-look, a Windows quick-look style asset inspector for CG workflows, and I need a few small accessors on top of openusd to surface stage metadata (upAxis, metersPerUnit) and detect broken references in user assets. These five PRs are the minimal set of additions I needed.

If the API and approach look reasonable to you, I'd be grateful if you could merge them. If not — totally fine to close, I'd just maintain them on my fork instead. Happy to revise based on any feedback.

Thanks for the great crate!

@mxpv
Copy link
Copy Markdown
Owner

mxpv commented Apr 7, 2026

Thanks for PR! I believe upAxis doesn't belong to Stage. In C++ version it belongs to UsdGeom module. The rationale is that these are geometry-level scene conventions, not core scene-description concepts.

This crate doesn't have a geometry module yet, but it's on the roadmap. In the meantime, you can access upAxis via the generic field API:

let up_axis: Option<String> = stage.field("/", "upAxis")?;

I'd prefer to defer the typed UpAxis enum and FieldKey until the geometry module lands, so it ends up in the right place architecturally. Going to close this one for now.

@mxpv mxpv closed this Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants