diff --git a/CLAUDE.md b/CLAUDE.md index 3d720a0..6ec07bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -86,7 +86,7 @@ The test suite includes extensive binary format tests using fixture files in `fi - Time-sampled data - Scene hierarchy traversal -Test fixtures are small USD files covering specific format features and edge cases. +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. ## Dependencies diff --git a/README.md b/README.md index 3d2d32f..e981717 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,9 @@ If you encounter a file that can't be read, please open an [issue](https://githu ## Example ```rust,no_run -use openusd::{ar, sdf::FieldKey, Stage}; +use openusd::{sdf::FieldKey, Stage}; -// Filesystem-based asset resolver. -let resolver = ar::DefaultResolver::new(); -let stage = Stage::open(&resolver, "scene.usda")?; +let stage = Stage::open("scene.usda")?; // Traverse all prims in the composed scene graph. stage.traverse(|path| { diff --git a/src/compose/mod.rs b/src/compose/mod.rs index 66fcc0b..825f3c1 100644 --- a/src/compose/mod.rs +++ b/src/compose/mod.rs @@ -20,6 +20,73 @@ use crate::sdf::schema::{ChildrenKey, FieldKey}; use crate::sdf::{AbstractData, Path, Value}; use crate::{usda, usdc}; +/// The kind of layer dependency that triggered a composition error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DependencyKind { + /// A sublayer declared on the layer's pseudo-root. + SubLayer, + /// A reference arc on a prim. + Reference, + /// A payload arc on a prim. + Payload, +} + +impl std::fmt::Display for DependencyKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SubLayer => write!(f, "sublayer"), + Self::Reference => write!(f, "reference"), + Self::Payload => write!(f, "payload"), + } + } +} + +/// An error encountered during stage composition that may be recoverable. +/// +/// When opening a stage, some errors (such as missing referenced files) can be +/// tolerated so that the stage is partially constructed. A callback provided via +/// [`StageBuilder::on_error`](crate::stage::StageBuilder::on_error) receives +/// these errors and decides whether to continue or abort. +#[derive(Debug)] +#[non_exhaustive] +pub enum CompositionError { + /// An asset path could not be resolved to a physical location. + UnresolvedAsset { + /// The asset path that could not be resolved. + asset_path: String, + /// The layer that declared this dependency. + referencing_layer: String, + /// What kind of composition arc declared this dependency. + kind: DependencyKind, + /// The prim that declared this arc (`None` for sublayers). + prim_path: Option, + }, +} + +impl std::fmt::Display for CompositionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnresolvedAsset { + asset_path, + referencing_layer, + kind, + prim_path, + } => { + write!( + f, + "failed to resolve {kind} asset: {asset_path} (referenced by {referencing_layer}" + )?; + if let Some(prim) = prim_path { + write!(f, " at {prim}")?; + } + write!(f, ")") + } + } + } +} + +impl std::error::Error for CompositionError {} + /// A single loaded layer in the composition. pub struct Layer { /// Resolved, canonical identifier for this layer. @@ -45,18 +112,40 @@ impl std::fmt::Debug for Layer { } } +/// A dependency discovered while walking a layer's scene graph. +struct Dependency { + /// The asset path to resolve. + asset_path: String, + /// What kind of composition arc declared this dependency. + kind: DependencyKind, + /// The prim that declared this arc (`None` for sublayers). + prim_path: Option, +} + /// Opens a root layer and recursively collects all referenced layers. /// -/// Walks sublayers (layer-level), then traverses every prim to collect -/// references and payloads. Each discovered asset path is resolved via the -/// provided [`Resolver`], loaded, and itself walked for further references. +/// Any unresolvable transitive dependency causes an immediate error. +/// For more control over error handling, use [`collect_layers_with_handler`]. /// /// Returns a [`Vec`] with the root (strongest) layer first. pub fn collect_layers(resolver: &impl Resolver, root_path: &str) -> Result> { + collect_layers_with_handler(resolver, root_path, |e| bail!("{e}")) +} + +/// Like [`collect_layers`] but with a custom error handler for recoverable +/// composition failures. +/// +/// The `on_error` callback decides whether to continue (`Ok(())`) or abort +/// (`Err(...)`) for each composition error encountered. +pub(crate) fn collect_layers_with_handler( + resolver: &impl Resolver, + root_path: &str, + on_error: impl Fn(CompositionError) -> Result<()>, +) -> Result> { let mut layers = Vec::new(); let mut visited = HashSet::new(); - collect_recursive(resolver, root_path, None, &mut layers, &mut visited)?; + collect_recursive(resolver, root_path, None, &mut layers, &mut visited, &on_error)?; // Layers are collected in post-order (leaves first), reverse so root is first. layers.reverse(); @@ -71,6 +160,7 @@ fn collect_recursive( anchor: Option<&ar::ResolvedPath>, layers: &mut Vec, visited: &mut HashSet, + on_error: &dyn Fn(CompositionError) -> Result<()>, ) -> Result<()> { // Create an anchored identifier so relative paths resolve correctly. let identifier = resolver.create_identifier(asset_path, anchor); @@ -81,6 +171,7 @@ fn collect_recursive( } // Resolve using the anchored identifier (which is absolute). + // Root layer (no anchor) must always resolve — missing root is a hard error. let resolved = resolver .resolve(&identifier) .with_context(|| format!("failed to resolve asset path: {asset_path}"))?; @@ -93,23 +184,36 @@ fn collect_recursive( // Read expression variables from this layer's pseudo-root. let expr_vars = read_expression_variables(data.as_ref()); - // Collect all asset paths that need recursive loading. - let raw_paths = collect_asset_paths(data.as_ref()); + // Collect typed dependencies from this layer. + let deps = collect_dependencies(data.as_ref()); - // Evaluate any expression-valued asset paths. - let referenced = resolve_expressions(&raw_paths, &expr_vars)?; - - // Recurse into discovered layers, anchored to the current layer. let is_usdz = resolved.extension().and_then(|e| e.to_str()) == Some("usdz"); - if is_usdz && !referenced.is_empty() { - bail!( - "cross-file references within USDZ archives are not yet supported: {}", - resolved - ); - } - for ref_path in &referenced { - collect_recursive(resolver, ref_path, Some(&resolved), layers, visited)?; + for dep in deps { + // Evaluate expression-valued asset paths. + let dep_asset = resolve_expression(&dep.asset_path, &expr_vars)?; + + if is_usdz { + bail!( + "cross-file references within USDZ archives are not yet supported: {}", + resolved + ); + } + + // Check if this dependency resolves before recursing. + let dep_id = resolver.create_identifier(&dep_asset, Some(&resolved)); + if !visited.contains(&dep_id) && resolver.resolve(&dep_id).is_none() { + on_error(CompositionError::UnresolvedAsset { + asset_path: dep_asset, + referencing_layer: identifier.clone(), + kind: dep.kind, + prim_path: dep.prim_path, + })?; + visited.insert(dep_id); + continue; + } + + collect_recursive(resolver, &dep_asset, Some(&resolved), layers, visited, on_error)?; } layers.push(Layer::new(identifier, data)); @@ -117,16 +221,22 @@ fn collect_recursive( Ok(()) } -/// Collects all asset paths from sublayers, references, and payloads in a layer. -fn collect_asset_paths(data: &dyn AbstractData) -> Vec { - let mut paths = Vec::new(); +/// Collects typed dependencies from sublayers, references, and payloads in a layer. +fn collect_dependencies(data: &dyn AbstractData) -> Vec { + let mut deps = Vec::new(); let root = Path::abs_root(); // Sublayers (layer-level). if let Ok(value) = data.get(&root, FieldKey::SubLayers.as_str()) { if let Value::StringVec(sub_paths) = value.into_owned() { - paths.extend(sub_paths); + for asset_path in sub_paths { + deps.push(Dependency { + asset_path, + kind: DependencyKind::SubLayer, + prim_path: None, + }); + } } } @@ -135,16 +245,42 @@ fn collect_asset_paths(data: &dyn AbstractData) -> Vec { for prim_path in &prim_paths { // References. if let Ok(value) = data.get(prim_path, FieldKey::References.as_str()) { - extract_reference_paths(&value, &mut paths); + if let Value::ReferenceListOp(list_op) = value.as_ref() { + for r in list_op.iter().filter(|r| !r.asset_path.is_empty()) { + deps.push(Dependency { + asset_path: r.asset_path.clone(), + kind: DependencyKind::Reference, + prim_path: Some(prim_path.clone()), + }); + } + } } // Payloads. if let Ok(value) = data.get(prim_path, FieldKey::Payload.as_str()) { - extract_payload_paths(&value, &mut paths); + match value.as_ref() { + Value::Payload(p) if !p.asset_path.is_empty() => { + deps.push(Dependency { + asset_path: p.asset_path.clone(), + kind: DependencyKind::Payload, + prim_path: Some(prim_path.clone()), + }); + } + Value::PayloadListOp(list_op) => { + for p in list_op.iter().filter(|p| !p.asset_path.is_empty()) { + deps.push(Dependency { + asset_path: p.asset_path.clone(), + kind: DependencyKind::Payload, + prim_path: Some(prim_path.clone()), + }); + } + } + _ => {} + } } } - paths + deps } /// Collects all prim paths by walking the `primChildren` hierarchy. @@ -208,13 +344,6 @@ pub fn open_layer(resolver: &impl Resolver, resolved: &ar::ResolvedPath) -> Resu } } -/// Appends external asset paths from a references value. -fn extract_reference_paths(value: &Value, out: &mut Vec) { - if let Value::ReferenceListOp(list_op) = value { - out.extend(list_op.iter().map(|r| &r.asset_path).filter(|p| !p.is_empty()).cloned()); - } -} - /// Reads `expressionVariables` from the layer's pseudo-root, if present. fn read_expression_variables(data: &dyn AbstractData) -> HashMap { let root = Path::abs_root(); @@ -226,43 +355,26 @@ fn read_expression_variables(data: &dyn AbstractData) -> HashMap HashMap::new() } -/// Evaluates expression-valued asset paths, passing through plain paths unchanged. -fn resolve_expressions(paths: &[String], vars: &HashMap) -> Result> { - paths - .iter() - .map(|path| { - if expr::is_expression(path) { - let expression = - expr::Expr::parse(path).with_context(|| format!("failed to parse expression: {path}"))?; - let result = expression - .eval(vars) - .with_context(|| format!("failed to evaluate expression: {path}"))?; - match result { - Value::String(s) => Ok(s), - other => bail!("expression must evaluate to a string, got: {other:?}"), - } - } else { - Ok(path.clone()) - } - }) - .collect() -} - -/// Appends external asset paths from a payload value. -fn extract_payload_paths(value: &Value, out: &mut Vec) { - match value { - Value::Payload(p) if !p.asset_path.is_empty() => { - out.push(p.asset_path.clone()); - } - Value::PayloadListOp(list_op) => { - out.extend(list_op.iter().map(|p| &p.asset_path).filter(|p| !p.is_empty()).cloned()); +/// Evaluates an expression-valued asset path, or passes it through unchanged. +fn resolve_expression(path: &str, vars: &HashMap) -> Result { + if expr::is_expression(path) { + let expression = expr::Expr::parse(path).with_context(|| format!("failed to parse expression: {path}"))?; + let result = expression + .eval(vars) + .with_context(|| format!("failed to evaluate expression: {path}"))?; + match result { + Value::String(s) => Ok(s), + other => bail!("expression must evaluate to a string, got: {other:?}"), } - _ => {} + } else { + Ok(path.to_string()) } } #[cfg(test)] mod tests { + use std::cell::RefCell; + use super::*; use crate::ar::DefaultResolver; @@ -466,4 +578,77 @@ mod tests { Ok(()) } + + // ----------------------------------------------------------------------- + // Error handling + // ----------------------------------------------------------------------- + + /// Default handler errors on unresolvable dependencies (backward compat). + #[test] + fn collect_layers_errors_on_missing_reference() { + let path = composition_path("references/reference_invalid.usda"); + let resolver = DefaultResolver::new(); + assert!(collect_layers(&resolver, &path).is_err()); + } + + /// Custom handler receives correct error details for each dependency kind. + #[test] + fn handler_receives_error() -> Result<()> { + let resolver = DefaultResolver::new(); + let errors = RefCell::new(Vec::new()); + + let path = composition_path("references/reference_invalid.usda"); + collect_layers_with_handler(&resolver, &path, |e| { + errors.borrow_mut().push(e); + Ok(()) + })?; + + let path = composition_path("payload/payload_invalid.usda"); + collect_layers_with_handler(&resolver, &path, |e| { + errors.borrow_mut().push(e); + Ok(()) + })?; + + let path = composition_path("subLayer/sublayer_invalid.usda"); + collect_layers_with_handler(&resolver, &path, |e| { + errors.borrow_mut().push(e); + Ok(()) + })?; + + let errors = errors.into_inner(); + assert_eq!(errors.len(), 3); + + let CompositionError::UnresolvedAsset { + kind, ref prim_path, .. + } = errors[0]; + assert_eq!(kind, DependencyKind::Reference); + assert_eq!(prim_path.as_ref().unwrap().as_str(), "/World/invalid_reference"); + + let CompositionError::UnresolvedAsset { + kind, ref prim_path, .. + } = errors[1]; + assert_eq!(kind, DependencyKind::Payload); + assert_eq!(prim_path.as_ref().unwrap().as_str(), "/World/invalid_payload"); + + let CompositionError::UnresolvedAsset { + kind, ref prim_path, .. + } = errors[2]; + assert_eq!(kind, DependencyKind::SubLayer); + assert!(prim_path.is_none()); + + Ok(()) + } + + /// Handler that ignores all errors allows partial layer collection. + #[test] + fn handler_can_ignore_errors() -> Result<()> { + let path = composition_path("references/reference_invalid.usda"); + let resolver = DefaultResolver::new(); + let layers = collect_layers_with_handler(&resolver, &path, |_| Ok(()))?; + + // Root layer loads despite the missing reference. + assert_eq!(layers.len(), 1); + assert!(layers[0].identifier.contains("reference_invalid.usda")); + Ok(()) + } } diff --git a/src/lib.rs b/src/lib.rs index 6713cc8..7a9a192 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,10 +19,9 @@ //! # Quick start //! //! ```no_run -//! use openusd::{ar, Stage}; +//! use openusd::Stage; //! -//! let resolver = ar::DefaultResolver::new(); -//! let stage = Stage::open(&resolver, "scene.usda").unwrap(); +//! let stage = Stage::open("scene.usda").unwrap(); //! //! stage.traverse(|prim_path| { //! println!("{prim_path}"); @@ -38,5 +37,6 @@ pub mod usda; pub mod usdc; pub mod usdz; +pub use compose::{CompositionError, DependencyKind}; pub use half::f16; -pub use stage::Stage; +pub use stage::{Stage, StageBuilder}; diff --git a/src/stage.rs b/src/stage.rs index ca6e38d..06cb179 100644 --- a/src/stage.rs +++ b/src/stage.rs @@ -28,9 +28,10 @@ use std::collections::HashMap; use anyhow::Result; -use crate::ar::Resolver; +use crate::ar::{DefaultResolver, Resolver}; use crate::compose; use crate::compose::prim_index::{ArcType, Node, PrimIndex}; +use crate::compose::CompositionError; use crate::sdf::schema::{ChildrenKey, FieldKey}; use crate::sdf::{AbstractData, ListOp, Path, Payload, Reference, SpecType, Value}; @@ -48,13 +49,35 @@ pub struct Stage { } impl Stage { - /// Opens a stage from a root layer file. + /// Opens a stage from a root layer file using the [`DefaultResolver`]. + /// + /// Any unresolvable transitive dependency causes an immediate error. + /// For custom resolver or error handling, use [`Stage::builder`]. + pub fn open(root_path: &str) -> Result { + Self::builder().open(root_path) + } + + /// Creates a [`StageBuilder`] for configuring how the stage is opened. /// - /// Recursively resolves and loads all referenced layers, then builds a - /// composed stage ready for queries. - pub fn open(resolver: &impl Resolver, root_path: &str) -> Result { - let collected = compose::collect_layers(resolver, root_path)?; + /// # Example + /// + /// ```no_run + /// use openusd::Stage; + /// + /// let stage = Stage::builder() + /// .on_error(|err| { + /// eprintln!("warning: {err}"); + /// Ok(()) + /// }) + /// .open("scene.usda") + /// .unwrap(); + /// ``` + pub fn builder() -> StageBuilder { + StageBuilder::new() + } + /// Constructs a stage from pre-collected layers. + fn from_layers(collected: Vec) -> Self { let mut identifiers = Vec::with_capacity(collected.len()); let mut layers = Vec::with_capacity(collected.len()); @@ -63,11 +86,11 @@ impl Stage { layers.push(layer.data); } - Ok(Self { + Self { layers, identifiers, prim_indices: RefCell::new(HashMap::new()), - }) + } } /// Returns the number of layers in the stage. @@ -508,10 +531,64 @@ impl Stage { } } +/// Default composition error handler that treats all errors as fatal. +type StrictErrorHandler = fn(CompositionError) -> Result<()>; + +/// Converts a composition error into a hard failure. +fn strict_composition_error(e: CompositionError) -> Result<()> { + Err(anyhow::anyhow!("{e}")) +} + +/// Builder for configuring and opening a [`Stage`]. +/// +/// Created via [`Stage::builder`]. Allows setting a custom asset resolver +/// and an error handler for recoverable composition failures. +pub struct StageBuilder Result<()> = StrictErrorHandler> { + resolver: R, + on_error: E, +} + +impl StageBuilder { + fn new() -> Self { + Self { + resolver: DefaultResolver::new(), + on_error: strict_composition_error, + } + } +} + +impl Result<()>> StageBuilder { + /// Sets a custom asset resolver. + pub fn resolver(self, resolver: R2) -> StageBuilder { + StageBuilder { + resolver, + on_error: self.on_error, + } + } + + /// Sets a callback invoked when a recoverable composition error occurs. + /// + /// Return `Ok(())` to skip the problematic dependency and continue, + /// or `Err(...)` to abort composition. + /// + /// By default, all composition errors are fatal. + pub fn on_error Result<()>>(self, handler: E2) -> StageBuilder { + StageBuilder { + resolver: self.resolver, + on_error: handler, + } + } + + /// Opens a stage from a root layer file. + pub fn open(self, root_path: &str) -> Result { + let collected = compose::collect_layers_with_handler(&self.resolver, root_path, self.on_error)?; + Ok(Stage::from_layers(collected)) + } +} + #[cfg(test)] mod tests { use super::*; - use crate::ar::DefaultResolver; use crate::compose::prim_index::ArcType; const VENDOR_COMPOSITION: &str = "vendor/usd-wg-assets/test_assets/foundation/stage_composition"; @@ -534,8 +611,7 @@ mod tests { #[test] fn find_layer_exact_match() -> Result<()> { let path = fixture_path("ref_external.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // The full identifier of the root layer should match exactly. assert!( @@ -551,8 +627,7 @@ mod tests { #[test] fn find_layer_suffix_match() -> Result<()> { let path = fixture_path("ref_external.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // ref_external.usda references ref_target.usda, which should be loaded. let result = stage.find_layer("ref_target.usda"); @@ -566,8 +641,7 @@ mod tests { #[test] fn find_layer_no_partial_name_match() -> Result<()> { let path = fixture_path("ref_external.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; assert!( stage.find_layer("target.usda").is_none(), @@ -581,8 +655,7 @@ mod tests { #[test] fn find_layer_not_found() -> Result<()> { let path = fixture_path("ref_external.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; assert!(stage.find_layer("nonexistent.usda").is_none()); @@ -596,8 +669,7 @@ mod tests { #[test] fn prim_index_single_layer() -> Result<()> { let path = composition_path("active.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let index = stage.prim_index(&Path::new("/World")?); assert_eq!(index.nodes.len(), 1); @@ -613,8 +685,7 @@ mod tests { fn prim_index_sublayer_two_layers() -> Result<()> { // sublayer_override.usda sublayers sublayer_base.usda; both have /World. let path = fixture_path("sublayer_override.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let index = stage.prim_index(&Path::new("/World")?); assert_eq!(index.nodes.len(), 2, "both layers should have /World"); @@ -628,8 +699,7 @@ mod tests { #[test] fn prim_index_prim_only_in_stronger_layer() -> Result<()> { let path = fixture_path("sublayer_override.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // /World/Sphere is only defined in the override layer. let index = stage.prim_index(&Path::new("/World/Sphere")?); @@ -643,8 +713,7 @@ mod tests { #[test] fn prim_index_nonexistent() -> Result<()> { let path = composition_path("active.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let index = stage.prim_index(&Path::new("/DoesNotExist")?); assert!(index.is_empty()); @@ -659,8 +728,7 @@ mod tests { #[test] fn open_single_layer() -> Result<()> { let path = composition_path("active.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; assert_eq!(stage.layer_count(), 1); assert_eq!(stage.default_prim(), Some("World".to_string())); @@ -673,8 +741,7 @@ mod tests { #[test] fn traverse_single_layer() -> Result<()> { let path = composition_path("active.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let mut prims = Vec::new(); stage.traverse(|p| prims.push(p.as_str().to_string()))?; @@ -688,8 +755,7 @@ mod tests { #[test] fn field_single_layer() -> Result<()> { let path = composition_path("active.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // The "active" metadata on CubeInactive should be false. let active = stage.field::("/World/CubeInactive", FieldKey::Active)?; @@ -706,8 +772,7 @@ mod tests { #[test] fn field_not_authored() -> Result<()> { let path = composition_path("active.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let active = stage.field::("/World", FieldKey::Active)?; assert_eq!(active, None); @@ -723,8 +788,7 @@ mod tests { #[test] fn sublayer_stronger_opinion_wins() -> Result<()> { let path = fixture_path("sublayer_override.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; assert_eq!(stage.layer_count(), 2); @@ -748,8 +812,7 @@ mod tests { #[test] fn sublayer_children_union() -> Result<()> { let path = fixture_path("sublayer_override.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let children = stage.prim_children("/World")?; // Override layer adds Sphere; base layer defines Cube. @@ -764,8 +827,7 @@ mod tests { #[test] fn sublayer_prims_from_weaker_layer() -> Result<()> { let path = composition_path("subLayer/sublayer_same_folder.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; assert_eq!(stage.layer_count(), 2); assert_eq!(stage.default_prim(), Some("World".to_string())); @@ -783,8 +845,7 @@ mod tests { #[test] fn field_active_metadata() -> Result<()> { let path = composition_path("active.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let inactive: Option = stage.field("/World/CubeInactive", FieldKey::Active)?; assert_eq!(inactive, Some(false)); @@ -804,8 +865,7 @@ mod tests { #[test] fn reference_external_default_prim() -> Result<()> { let path = fixture_path("ref_external.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // /World/MyPrim should exist via the reference. assert!(stage.has_spec("/World/MyPrim")); @@ -834,8 +894,7 @@ mod tests { #[test] fn reference_default_prim_from_external_layer() -> Result<()> { let path = composition_path("references/reference_same_folder.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // /World references _stage.usda's defaultPrim ("World"), // so /World/Cube should come from the referenced layer. @@ -854,8 +913,7 @@ mod tests { #[test] fn reference_explicit_prim_path() -> Result<()> { let path = fixture_path("ref_prim.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // /World/RefPrim should exist with a Reference arc. let index = stage.prim_index(&Path::new("/World/RefPrim")?); @@ -881,8 +939,7 @@ mod tests { #[test] fn inherit_from_class() -> Result<()> { let path = composition_path("class_inherit.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // The prim index for cubeWithoutSetColor should include an Inherit node // pointing at /_myClass. @@ -908,8 +965,7 @@ mod tests { #[test] fn inherit_local_opinion_wins() -> Result<()> { let path = composition_path("class_inherit.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // cubeWithSetColor has both a local and inherited displayColor. // The prim index should have Root first, then Inherit. @@ -941,8 +997,7 @@ mod tests { "{}/vendor/usd-wg-assets/docs/CompositionPuzzles/VariantSetAndLocal1/puzzle_1.usda", manifest_dir() ); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // The prim index should contain a Variant arc node. let index = stage.prim_index(&Path::new("/World/Sphere")?); @@ -965,8 +1020,7 @@ mod tests { "{}/vendor/usd-wg-assets/docs/CompositionPuzzles/VariantSetAndLocal1/puzzle_1.usda", manifest_dir() ); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // The local radius=1 should win over variant radius=2. let prop = Path::new("/World/Sphere")?.append_property("radius")?; @@ -983,8 +1037,7 @@ mod tests { #[test] fn payload_pulls_children() -> Result<()> { let path = composition_path("payload/payload_same_folder.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; // The payload target layer has /World/Cube. Since /World is the payload // target, /World/Cube should appear. @@ -1005,8 +1058,7 @@ mod tests { #[test] fn specialize_arc_present() -> Result<()> { let path = composition_path("inherit_and_specialize.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let index = stage.prim_index(&Path::new("/World/cubeScene/specializes")?); assert!( @@ -1022,8 +1074,7 @@ mod tests { #[test] fn specialize_local_opinion_wins() -> Result<()> { let path = composition_path("inherit_and_specialize.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let prop = Path::new("/World/cubeScene/specializes")?.append_property("primvars:displayColor")?; let value: Option = stage.field(&prop, FieldKey::Default)?; @@ -1041,8 +1092,7 @@ mod tests { #[test] fn instanceable_true_parses_and_is_readable() -> Result<()> { let path = fixture_path("instanceable_metadata.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let value = stage.field::("/Root/InstancePrototype", FieldKey::Instanceable)?; assert_eq!(value, Some(true), "instanceable = true should be stored"); @@ -1054,8 +1104,7 @@ mod tests { #[test] fn instanceable_false_parses_and_is_readable() -> Result<()> { let path = fixture_path("instanceable_metadata.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let value = stage.field::("/Root/NotInstanceable", FieldKey::Instanceable)?; assert_eq!(value, Some(false), "instanceable = false should be stored"); @@ -1067,8 +1116,7 @@ mod tests { #[test] fn instanceable_absent_returns_none() -> Result<()> { let path = fixture_path("instanceable_metadata.usda"); - let resolver = DefaultResolver::new(); - let stage = Stage::open(&resolver, &path)?; + let stage = Stage::open(&path)?; let value = stage.field::("/Root", FieldKey::Instanceable)?; assert_eq!(value, None, "instanceable should be None when not authored");