diff --git a/fixtures/up_axis_absent.usda b/fixtures/up_axis_absent.usda new file mode 100644 index 0000000..99aaac8 --- /dev/null +++ b/fixtures/up_axis_absent.usda @@ -0,0 +1,8 @@ +#usda 1.0 +( + defaultPrim = "Root" +) + +def Xform "Root" +{ +} diff --git a/fixtures/up_axis_y.usda b/fixtures/up_axis_y.usda new file mode 100644 index 0000000..117f6dc --- /dev/null +++ b/fixtures/up_axis_y.usda @@ -0,0 +1,9 @@ +#usda 1.0 +( + upAxis = "Y" + defaultPrim = "Root" +) + +def Xform "Root" +{ +} diff --git a/fixtures/up_axis_z.usda b/fixtures/up_axis_z.usda new file mode 100644 index 0000000..a028f28 --- /dev/null +++ b/fixtures/up_axis_z.usda @@ -0,0 +1,9 @@ +#usda 1.0 +( + upAxis = "Z" + defaultPrim = "Root" +) + +def Xform "Root" +{ +} diff --git a/src/sdf/schema.rs b/src/sdf/schema.rs index e19b8fa..27e4239 100644 --- a/src/sdf/schema.rs +++ b/src/sdf/schema.rs @@ -62,6 +62,7 @@ pub enum FieldKey { VariantSetNames, EndFrame, StartFrame, + UpAxis, } impl From for &'static str { @@ -138,6 +139,7 @@ impl FieldKey { FieldKey::VariantSetNames => "variantSetNames", FieldKey::EndFrame => "endFrame", FieldKey::StartFrame => "startFrame", + FieldKey::UpAxis => "upAxis", } } } diff --git a/src/stage.rs b/src/stage.rs index 05d393a..b53d2a4 100644 --- a/src/stage.rs +++ b/src/stage.rs @@ -34,6 +34,20 @@ use crate::compose::prim_index::{ArcType, Node, PrimIndex}; use crate::sdf::schema::{ChildrenKey, FieldKey}; use crate::sdf::{AbstractData, ListOp, Path, Payload, Reference, SpecType, Value}; +/// The scene's up-axis, as stored in the root layer's `upAxis` metadata. +/// +/// USD defines two valid values: `"Y"` (Y-up, the default for many DCC tools) +/// and `"Z"` (Z-up, used by e.g. OpenUSD's own reference scenes). +/// +/// See +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UpAxis { + /// Y axis points up. + Y, + /// Z axis points up. + Z, +} + /// A composed USD stage. /// /// Owns the loaded layer stack and provides composed access to prims, @@ -85,6 +99,35 @@ impl Stage { self.field::(&Path::abs_root(), FieldKey::DefaultPrim).ok()? } + /// 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. + /// + /// # Example + /// + /// ```no_run + /// use openusd::{ar::DefaultResolver, Stage}; + /// use openusd::stage::UpAxis; + /// + /// let resolver = DefaultResolver::new(); + /// let stage = Stage::open(&resolver, "scene.usda").unwrap(); + /// match stage.up_axis() { + /// Some(UpAxis::Y) => println!("Y-up"), + /// Some(UpAxis::Z) => println!("Z-up"), + /// None => println!("upAxis not set"), + /// } + /// ``` + pub fn up_axis(&self) -> Option { + let raw = self.field::(&Path::abs_root(), FieldKey::UpAxis).ok()??; + match raw.as_str() { + "Y" => Some(UpAxis::Y), + "Z" => Some(UpAxis::Z), + _ => None, + } + } + /// Returns the composed list of root prim names (children of the pseudo-root). pub fn root_prims(&self) -> Result> { self.prim_children(Path::abs_root()) @@ -1035,4 +1078,42 @@ mod tests { Ok(()) } + + // --- Stage::up_axis() --- + + /// A stage with `upAxis = "Y"` should return `UpAxis::Y`. + #[test] + fn up_axis_y() -> Result<()> { + let path = fixture_path("up_axis_y.usda"); + let resolver = DefaultResolver::new(); + let stage = Stage::open(&resolver, &path)?; + + assert_eq!(stage.up_axis(), Some(UpAxis::Y)); + + Ok(()) + } + + /// A stage with `upAxis = "Z"` should return `UpAxis::Z`. + #[test] + fn up_axis_z() -> Result<()> { + let path = fixture_path("up_axis_z.usda"); + let resolver = DefaultResolver::new(); + let stage = Stage::open(&resolver, &path)?; + + assert_eq!(stage.up_axis(), Some(UpAxis::Z)); + + Ok(()) + } + + /// A stage without an `upAxis` metadata entry should return `None`. + #[test] + fn up_axis_absent_returns_none() -> Result<()> { + let path = fixture_path("up_axis_absent.usda"); + let resolver = DefaultResolver::new(); + let stage = Stage::open(&resolver, &path)?; + + assert_eq!(stage.up_axis(), None); + + Ok(()) + } }