Skip to main content

openusd/usdc/
mod.rs

1//! Binary file format (`usdc`) implementation.
2
3use std::{borrow::Cow, cell::RefCell, collections::HashMap, fmt::Debug, io, mem, path::Path};
4
5use anyhow::{Context, Result};
6use layout::ValueRep;
7
8mod coding;
9mod layout;
10mod reader;
11mod writer;
12
13pub use layout::{version, Version};
14pub use reader::{CrateFile, ReadExt};
15pub use writer::CrateWriter;
16
17use crate::{ar, sdf, tf};
18
19/// USDC binary format magic bytes (`PXR-USDC`).
20pub const MAGIC: &[u8] = b"PXR-USDC";
21
22/// A spec's type plus where its fields live. The crate stores fields
23/// deduplicated and shared across specs, so a spec read from the file keeps only
24/// the start of its fieldset; the fields are resolved on demand from the compact
25/// [`CrateFile`] arrays rather than expanded into a per-spec list. Fields
26/// authored after load are layered in a small overlay, leaving the backend both
27/// lazy and writable (C++ `Usd_CrateData` likewise keeps each field as either a
28/// `ValueRep` into the crate or an in-memory `VtValue`).
29#[derive(Debug)]
30struct Spec {
31    /// Specifies the type of an object.
32    ty: sdf::SpecType,
33    /// Start index into the file's `fieldsets` for the fields read from the
34    /// crate, or `None` for a spec created in memory.
35    fieldset: Option<usize>,
36    /// Fields authored after load, layered over the crate fields in authored
37    /// order. `Some` sets or overrides a field; `None` tombstones one so it
38    /// reads as absent even when the crate holds it.
39    authored: Vec<(String, Option<sdf::Value>)>,
40}
41
42impl Spec {
43    /// Returns the authored override for `field`: `Some(&Some(value))` when set,
44    /// `Some(&None)` when tombstoned, `None` when the crate fields alone decide.
45    fn authored(&self, field: &str) -> Option<&Option<sdf::Value>> {
46        self.authored.iter().find(|(k, _)| k == field).map(|(_, v)| v)
47    }
48
49    /// Sets the authored override for `field`, replacing any prior one.
50    fn set_authored(&mut self, field: &str, value: Option<sdf::Value>) {
51        if let Some(slot) = self.authored.iter_mut().find(|(k, _)| k == field) {
52            slot.1 = value;
53        } else {
54            self.authored.push((field.to_owned(), value));
55        }
56    }
57
58    /// Drops any authored override for `field`, restoring authored order so a
59    /// later set re-appends rather than reusing the old slot.
60    fn remove_authored(&mut self, field: &str) {
61        self.authored.retain(|(k, _)| k != field);
62    }
63}
64
65/// High level interface to binary data.
66pub struct CrateData<R> {
67    file: RefCell<CrateFile<R>>,
68    data: HashMap<sdf::Path, Spec>,
69}
70
71impl<R> CrateData<R>
72where
73    R: io::Read + io::Seek,
74{
75    /// Read binary data from any reader.
76    pub fn open(reader: R, safe: bool) -> Result<Self> {
77        let mut file = CrateFile::open(reader)?;
78
79        if safe {
80            file.validate()?;
81        }
82
83        // Index each spec by its path, recording only its type and the start of
84        // its fieldset. The fields themselves stay in the file's compact,
85        // deduplicated arrays and are resolved on demand.
86        let specs = mem::take(&mut file.specs);
87        let mut data = HashMap::with_capacity(specs.len());
88
89        for spec in &specs {
90            let path = file.paths[spec.path_index].clone();
91            data.insert(
92                path,
93                Spec {
94                    ty: spec.spec_type,
95                    fieldset: Some(spec.fieldset_index),
96                    authored: Vec::new(),
97                },
98            );
99        }
100
101        Ok(Self {
102            file: RefCell::new(file),
103            data,
104        })
105    }
106}
107
108impl<R> sdf::AbstractData for CrateData<R>
109where
110    R: io::Read + io::Seek,
111{
112    #[inline]
113    fn has_spec(&self, path: &sdf::Path) -> bool {
114        self.data.contains_key(path)
115    }
116
117    fn has_field(&self, path: &sdf::Path, field: &str) -> bool {
118        let Some(spec) = self.data.get(path) else {
119            return false;
120        };
121        if let Some(authored) = spec.authored(field) {
122            return authored.is_some();
123        }
124        match spec.fieldset {
125            Some(start) => crate_fields(&self.file.borrow(), start).any(|(name, _)| name == field),
126            None => false,
127        }
128    }
129
130    #[inline]
131    fn spec_type(&self, path: &sdf::Path) -> Option<sdf::SpecType> {
132        self.data.get(path).map(|spec| spec.ty)
133    }
134
135    fn try_field(&self, path: &sdf::Path, field: &str) -> Result<Option<Cow<'_, sdf::Value>>, sdf::DataError> {
136        let Some(spec) = self.data.get(path) else {
137            return Ok(None);
138        };
139        if let Some(authored) = spec.authored(field) {
140            return Ok(authored.as_ref().map(Cow::Borrowed));
141        }
142        let Some(start) = spec.fieldset else {
143            return Ok(None);
144        };
145        let rep = crate_fields(&self.file.borrow(), start)
146            .find(|(name, _)| *name == field)
147            .map(|(_, rep)| rep);
148        let Some(rep) = rep else {
149            return Ok(None);
150        };
151        // The crate value decoder still reports failures as `anyhow`; box it as
152        // the typed `DataError`'s source at this trait boundary.
153        let value = self.file.borrow_mut().value(rep).map_err(|e| sdf::DataError::Decode {
154            path: path.clone(),
155            field: field.to_owned(),
156            source: e.into(),
157        })?;
158        Ok(Some(Cow::Owned(value)))
159    }
160
161    fn list_fields(&self, path: &sdf::Path) -> Option<Vec<String>> {
162        let spec = self.data.get(path)?;
163        let mut names = Vec::new();
164        if let Some(start) = spec.fieldset {
165            for (name, _) in crate_fields(&self.file.borrow(), start) {
166                // A tombstoned crate field is skipped; an overridden one keeps
167                // its crate position and is not duplicated by the overlay pass.
168                if !matches!(spec.authored(name), Some(None)) {
169                    names.push(name.to_owned());
170                }
171            }
172        }
173        for (field, value) in &spec.authored {
174            if value.is_some() && !names.iter().any(|n| n == field) {
175                names.push(field.clone());
176            }
177        }
178        Some(names)
179    }
180
181    fn spec_paths(&self) -> Vec<sdf::Path> {
182        let mut paths: Vec<sdf::Path> = self.data.keys().cloned().collect();
183        paths.sort_by(|a, b| a.as_str().cmp(b.as_str()));
184        paths
185    }
186
187    fn create_spec(&mut self, path: sdf::Path, ty: sdf::SpecType) {
188        self.data.insert(
189            path,
190            Spec {
191                ty,
192                fieldset: None,
193                authored: Vec::new(),
194            },
195        );
196    }
197
198    fn erase_spec(&mut self, path: &sdf::Path) {
199        self.data.remove(path);
200    }
201
202    fn set_field(&mut self, path: &sdf::Path, field: &str, value: sdf::Value) {
203        match self.data.get_mut(path) {
204            Some(spec) => spec.set_authored(field, Some(value)),
205            None => debug_assert!(false, "set_field on absent spec at {path}"),
206        }
207    }
208
209    fn erase_field(&mut self, path: &sdf::Path, field: &str) {
210        let Some(spec) = self.data.get_mut(path) else {
211            return;
212        };
213        // A tombstone is only needed to mask a field the crate holds; for an
214        // authored-only or absent field, dropping the overlay entry keeps erase
215        // idempotent and lets a later set re-append in authored order.
216        let masks_crate = spec
217            .fieldset
218            .is_some_and(|start| crate_fields(&self.file.borrow(), start).any(|(name, _)| name == field));
219        if masks_crate {
220            spec.set_authored(field, None);
221        } else {
222            spec.remove_authored(field);
223        }
224    }
225}
226
227/// Walks a crate spec's fieldset from `start` to its terminator, yielding each
228/// field's resolved name and value representation. Names borrow the shared token
229/// table, so iterating makes no per-spec copy.
230fn crate_fields<R>(file: &CrateFile<R>, start: usize) -> impl Iterator<Item = (&str, ValueRep)> + '_ {
231    file.fieldsets
232        .get(start..)
233        .unwrap_or(&[])
234        .iter()
235        .map_while(|slot| *slot)
236        .map(move |index| {
237            let field = &file.fields[index];
238            (field_name(file, field.token_index), field.value_rep)
239        })
240}
241
242/// Resolves a crate token index to a field name, translating the crate's
243/// internal property-children token to the Sdf `propertyChildren` key the rest
244/// of the toolkit uses.
245fn field_name<R>(file: &CrateFile<R>, token_index: usize) -> &str {
246    let raw = file.tokens[token_index].as_str();
247    if raw == CRATE_PROPERTY_CHILDREN {
248        sdf::ChildrenKey::PropertyChildren.as_str()
249    } else {
250        raw
251    }
252}
253
254/// Read `usdc` data from a file on disk.
255pub fn read_file(path: impl AsRef<Path>) -> Result<Box<dyn sdf::AbstractData>> {
256    let file = std::fs::File::open(path)?;
257    let data = CrateData::open(file, true)?;
258
259    Ok(Box::new(data))
260}
261
262/// Binary crate format (`.usdc`) as an [`sdf::FileFormat`], wrapping
263/// [`CrateData`] and [`CrateWriter`]. Also the default writer for the ambiguous
264/// `.usd` extension (C++ `USD_WRITE_NEW_USD_FILES_AS_BINARY`).
265pub struct UsdcFileFormat;
266
267impl sdf::FileFormat for UsdcFileFormat {
268    fn format_id(&self) -> tf::Token {
269        tf::Token::new("usdc")
270    }
271
272    fn extensions(&self) -> &[&str] {
273        &["usdc", "usd"]
274    }
275
276    fn read(&self, resolver: &dyn ar::Resolver, resolved: &ar::ResolvedPath) -> Result<sdf::LayerData> {
277        let bytes = resolver.open_asset(resolved)?.read_all()?;
278        let data = CrateData::open(io::Cursor::new(bytes), true).context("failed to parse USDC layer")?;
279        Ok(Box::new(data))
280    }
281
282    fn matches_content(&self, prefix: &[u8]) -> bool {
283        prefix.starts_with(MAGIC)
284    }
285
286    fn write(&self, data: &dyn sdf::AbstractData, mut sink: &mut dyn sdf::WriteSeek) -> Result<()> {
287        CrateWriter::write(data, &mut sink)
288    }
289}
290
291/// The crate (binary) format names the property-children field "properties",
292/// while the rest of the toolkit uses the Sdf token "propertyChildren". The
293/// crate reader (`CrateData::open`) and writer translate between the two at that
294/// boundary, matching the reference crate implementation.
295const CRATE_PROPERTY_CHILDREN: &str = "properties";
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::gf;
301    use crate::gf::f16;
302    use std::path::Path;
303
304    #[test]
305    fn test_crate_hierarchy() -> Result<()> {
306        let path = Path::new("./vendor/usd-wg-assets/full_assets/ElephantWithMonochord/SoC-ElephantWithMonochord.usdc");
307        if !path.exists() {
308            eprintln!(
309                "Skipping test_crate_hierarchy: fixture not available at {}",
310                path.display()
311            );
312            return Ok(());
313        }
314
315        let data = read_file(path)?;
316
317        let prim_children: Vec<_> = data
318            .get_field(&sdf::Path::abs_root(), "primChildren")?
319            .into_owned()
320            .try_as_token_vec()
321            .unwrap();
322        assert_eq!(
323            prim_children.iter().map(|t| t.as_str()).collect::<Vec<_>>(),
324            ["SoC_ElephantWithMonochord"]
325        );
326
327        let elephant: Vec<_> = data
328            .get_field(&sdf::path("/SoC_ElephantWithMonochord")?, "primChildren")?
329            .into_owned()
330            .try_as_token_vec()
331            .unwrap();
332
333        assert_eq!(
334            elephant.iter().map(|t| t.as_str()).collect::<Vec<_>>(),
335            ["Materials", "Object", "CharacterAudioSource"]
336        );
337
338        let materials: Vec<_> = data
339            .get_field(&sdf::path("/SoC_ElephantWithMonochord/Materials")?, "primChildren")?
340            .into_owned()
341            .try_as_token_vec()
342            .unwrap();
343
344        assert_eq!(
345            materials.iter().map(|t| t.as_str()).collect::<Vec<_>>(),
346            ["Elefant_Mat_68050", "Monochord_Mat_68062"]
347        );
348
349        Ok(())
350    }
351
352    #[test]
353    fn test_read_custom_layer_data() {
354        let data = read_file("fixtures/fields.usdc").unwrap();
355
356        let custom_layer_data = data.get_field(&sdf::Path::abs_root(), "customLayerData").unwrap();
357
358        // customLayerData = {
359        //  string test = "Test string"
360        // }
361        let copyright = custom_layer_data
362            .try_as_dictionary_ref()
363            .unwrap()
364            .get("test")
365            .unwrap()
366            .try_as_string_ref()
367            .unwrap();
368
369        assert_eq!(copyright, "Test string");
370    }
371
372    #[test]
373    fn erase_then_reauthor_appends() {
374        let mut data = read_file("fixtures/fields.usdc").unwrap();
375        let path = sdf::path("/erase_order").unwrap();
376        data.create_spec(path.clone(), sdf::SpecType::Prim);
377
378        // Erasing an absent field is a no-op, and re-authoring a field appends
379        // it in authored order rather than reusing an earlier slot.
380        data.erase_field(&path, "alpha");
381        data.set_field(&path, "beta", sdf::Value::Token("b".into()));
382        data.set_field(&path, "alpha", sdf::Value::Token("a".into()));
383
384        assert_eq!(data.list_fields(&path).unwrap(), ["beta", "alpha"]);
385    }
386
387    #[test]
388    fn erase_masks_crate_field() {
389        let mut data = read_file("fixtures/fields.usdc").unwrap();
390        let root = sdf::Path::abs_root();
391        assert!(data.has_field(&root, "customLayerData"));
392
393        // A tombstone hides a field the crate holds.
394        data.erase_field(&root, "customLayerData");
395        assert!(!data.has_field(&root, "customLayerData"));
396        assert!(data.try_field(&root, "customLayerData").unwrap().is_none());
397    }
398
399    #[test]
400    fn test_read_bool() -> Result<()> {
401        let data = read_file("fixtures/fields.usdc")?;
402
403        let single = data
404            .get_field(&sdf::path("/World.flipNormals")?, "default")?
405            .into_owned()
406            .try_as_bool()
407            .unwrap();
408
409        assert!(single);
410
411        let bool_array = data
412            .get_field(&sdf::path("/World.boolArray")?, "default")?
413            .into_owned()
414            .try_as_bool_vec()
415            .unwrap();
416
417        assert_eq!(bool_array, vec![true, true, false, false, true, false]);
418
419        Ok(())
420    }
421
422    #[test]
423    fn test_read_chars() -> Result<()> {
424        let data = read_file("fixtures/fields.usdc")?;
425
426        let single_char = data
427            .get_field(&sdf::path("/World.singleChar")?, "default")?
428            .into_owned()
429            .try_as_uchar()
430            .unwrap();
431
432        assert_eq!(single_char, 128);
433
434        let char_array = data
435            .get_field(&sdf::path("/World.chars")?, "default")?
436            .into_owned()
437            .try_as_uchar_vec()
438            .unwrap();
439
440        assert_eq!(char_array, vec![128, 129, 130, 131, 132, 133, 134, 135, 136, 137]);
441
442        Ok(())
443    }
444
445    #[test]
446    fn test_read_quat_floats() -> Result<()> {
447        let data = read_file("fixtures/fields.usdc")?;
448
449        let quat = data
450            .get_field(&sdf::path("/World.quatfSingle")?, "default")?
451            .into_owned()
452            .try_as_quatf()
453            .unwrap();
454
455        // USDC bytes are `[x, y, z, w]` (Pixar GfQuat layout); the
456        // reader reorders to `(w, x, y, z)` to match USDA convention.
457        assert_eq!(quat, gf::quatf(1.4, 2.9, 8.5, 4.6));
458
459        let quat = data
460            .get_field(&sdf::path("/World.quatfArr")?, "default")?
461            .into_owned()
462            .try_as_quatf_vec()
463            .unwrap();
464
465        assert_eq!(
466            quat,
467            vec![
468                gf::quatf(4.2, 3.5, 2.6, 3.6), // 1
469                gf::quatf(2.4, 5.3, 6.3, 5.2), // 2
470                gf::quatf(7.1, 4.3, 2.4, 6.4), // 3
471            ]
472        );
473
474        Ok(())
475    }
476
477    #[test]
478    fn test_read_quat_doubles() -> Result<()> {
479        let data = read_file("fixtures/fields.usdc")?;
480
481        let quat = data
482            .get_field(&sdf::path("/World.quatdSingle")?, "default")?
483            .into_owned()
484            .try_as_quatd()
485            .unwrap();
486
487        // USDC bytes are `[x, y, z, w]`; reader returns `(w, x, y, z)`.
488        assert_eq!(quat, gf::quatd(2.4, 5.3, 6.3, 5.2));
489
490        let quat = data
491            .get_field(&sdf::path("/World.quatdArr")?, "default")?
492            .into_owned()
493            .try_as_quatd_vec()
494            .unwrap();
495
496        assert_eq!(
497            quat,
498            vec![
499                gf::quatd(4.2, 3.5, 2.6, 3.6), // 1
500                gf::quatd(7.1, 4.3, 2.4, 6.4), // 2
501            ]
502        );
503
504        Ok(())
505    }
506
507    #[test]
508    fn test_read_quat_half() -> Result<()> {
509        let data = read_file("fixtures/fields.usdc")?;
510
511        let quat = data
512            .get_field(&sdf::path("/World.quathSingle")?, "default")?
513            .into_owned()
514            .try_as_quath()
515            .unwrap();
516
517        // USDC bytes are `[x, y, z, w]`; reader returns `(w, x, y, z)`.
518        assert_eq!(
519            quat,
520            gf::quath(
521                f16::from_f32(3.5),
522                f16::from_f32(4.6),
523                f16::from_f32(2.5),
524                f16::from_f32(7.6)
525            )
526        );
527
528        let quat = data
529            .get_field(&sdf::path("/World.quathArr")?, "default")?
530            .into_owned()
531            .try_as_quath_vec()
532            .unwrap();
533
534        assert_eq!(
535            quat,
536            vec![
537                gf::quath(
538                    f16::from_f32(4.7),
539                    f16::from_f32(2.4),
540                    f16::from_f32(7.8),
541                    f16::from_f32(8.5)
542                ), // 1
543                gf::quath(
544                    f16::from_f32(4.6),
545                    f16::from_f32(6.7),
546                    f16::from_f32(5.6),
547                    f16::from_f32(5.3)
548                ), // 2
549            ]
550        );
551
552        Ok(())
553    }
554
555    #[test]
556    fn test_read_sub_layers() -> Result<()> {
557        let data = read_file("fixtures/expressions.usdc")?;
558
559        let sub_layer_offsets = data
560            .get_field(&sdf::path("/")?, "subLayerOffsets")?
561            .into_owned()
562            .try_as_layer_offset_vec()
563            .unwrap()
564            .into_iter()
565            .next()
566            .unwrap();
567
568        assert_eq!(sub_layer_offsets.offset, 0.0);
569        assert_eq!(sub_layer_offsets.scale, 1.0);
570
571        let sub_layers = data
572            .get_field(&sdf::path("/")?, "subLayers")?
573            .into_owned()
574            .try_as_string_vec()
575            .unwrap();
576        assert_eq!(sub_layers, vec!["`\"render_pass_${RENDER_PASS}.usd\"`"]);
577
578        Ok(())
579    }
580
581    #[test]
582    fn test_read_variant_selection() -> Result<()> {
583        let data = read_file("fixtures/expressions.usdc")?;
584
585        // prepend variantSets = "displayVariantSet"
586        let variant_set_names = data
587            .get_field(&sdf::path("/asset1")?, "variantSetNames")?
588            .into_owned()
589            .try_as_string_list_op()
590            .unwrap();
591        assert_eq!(variant_set_names.prepended_items, vec!["displayVariantSet".to_string()]);
592
593        let variant_selection = data
594            .get_field(&sdf::path("/asset1")?, "variantSelection")?
595            .into_owned()
596            .try_as_variant_selection_map()
597            .unwrap();
598
599        assert_eq!(variant_selection.len(), 1);
600        assert_eq!(
601            variant_selection.get("displayVariantSet").unwrap(),
602            "`${VARIANT_CHOICE}`"
603        );
604
605        Ok(())
606    }
607
608    #[test]
609    fn test_read_connection() -> Result<()> {
610        let data = read_file("fixtures/connection.usdc")?;
611
612        let conn = data
613            .get_field(&sdf::path("/boardMat/stReader.inputs:varname")?, "connectionPaths")?
614            .into_owned()
615            .try_as_path_list_op()
616            .unwrap();
617
618        assert!(conn.explicit);
619        assert_eq!(
620            conn.explicit_items,
621            vec![sdf::path("/TexModel/boardMat.inputs:frame:stPrimvarName")?]
622        );
623
624        let conn = data
625            .get_field(&sdf::path("/boardMat.outputs:surface")?, "connectionPaths")?
626            .into_owned()
627            .try_as_path_list_op()
628            .unwrap();
629
630        assert!(conn.explicit);
631        assert_eq!(
632            conn.explicit_items,
633            vec![sdf::path("/TexModel/boardMat/PBRShader.outputs:surface")?]
634        );
635
636        Ok(())
637    }
638
639    #[test]
640    fn test_read_reference() -> Result<()> {
641        let data = read_file("fixtures/reference.usdc")?;
642
643        let references = data
644            .get_field(&sdf::path("/MarbleCollection/Marble_Red")?, "references")?
645            .into_owned()
646            .try_as_reference_list_op()
647            .unwrap();
648
649        assert!(references.appended_items.is_empty());
650        assert!(references.deleted_items.is_empty());
651        assert!(references.ordered_items.is_empty());
652
653        assert!(references.explicit);
654        assert_eq!(references.explicit_items.len(), 1);
655
656        assert_eq!(references.explicit_items[0].asset_path, "Marble.usd");
657        assert_eq!(references.explicit_items[0].prim_path, sdf::path("/Foo/Bar")?);
658
659        Ok(())
660    }
661
662    #[test]
663    fn test_read_payload() -> Result<()> {
664        let data = read_file("fixtures/payload.usdc")?;
665
666        let payload = data
667            .get_field(&sdf::path("/MySphere1")?, "payload")?
668            .into_owned()
669            .try_as_payload()
670            .unwrap();
671
672        assert_eq!(payload.asset_path, "./payload.usda");
673        assert_eq!(payload.prim_path, sdf::path("/MySphere")?);
674
675        assert!(payload.layer_offset.is_some());
676
677        let layer_offset = payload.layer_offset.unwrap();
678        assert_eq!(layer_offset.offset, 0.0);
679        assert_eq!(layer_offset.scale, 1.0);
680
681        let payload_list_op = data
682            .get_field(&sdf::path("/MySphere2")?, "payload")?
683            .into_owned()
684            .try_as_payload_list_op()
685            .unwrap();
686
687        assert!(!payload_list_op.explicit);
688
689        assert!(payload_list_op.explicit_items.is_empty());
690        assert!(payload_list_op.added_items.is_empty());
691        assert!(payload_list_op.appended_items.is_empty());
692        assert!(payload_list_op.deleted_items.is_empty());
693        assert!(payload_list_op.ordered_items.is_empty());
694
695        assert_eq!(payload_list_op.prepended_items.len(), 1);
696        assert_eq!(payload_list_op.prepended_items[0].asset_path, "./cube_payload.usda");
697        assert_eq!(payload_list_op.prepended_items[0].prim_path, sdf::path("/PayloadCube")?);
698
699        Ok(())
700    }
701
702    #[test]
703    fn test_read_doubles() -> Result<()> {
704        let data = read_file("fixtures/floats.usdc")?;
705
706        let single = data
707            .get_field(&sdf::path("/PrimD.single")?, "default")?
708            .into_owned()
709            .try_as_double()
710            .unwrap();
711        assert_eq!(single, 4.3_f64);
712
713        let array = data
714            .get_field(&sdf::path("/PrimD.simple")?, "default")?
715            .into_owned()
716            .try_as_double_vec()
717            .unwrap();
718        assert_eq!(array, vec![0.5, 1.7, 2.4, 3.5, 4.9, 5.3, 6.2, 7.8, 8.6, 9.3]);
719
720        let compressed = data
721            .get_field(&sdf::path("/PrimD.copressed")?, "default")?
722            .into_owned()
723            .try_as_double_vec()
724            .unwrap();
725        assert_eq!(
726            compressed,
727            vec![
728                0.5, 1.7, 2.4, 3.5, 4.9, 5.3, 6.2, 7.8, 8.6, 9.3, 5.3, 6.2, 7.8, 8.6, 9.3, 0.5, 1.7, 2.4, 3.5, 4.9,
729                0.5, 1.7, 2.4, 3.5, 4.9, 5.3, 6.2, 7.8, 8.6, 9.3, 5.3, 6.2, 7.8, 8.6, 9.3, 0.5, 1.7, 2.4, 3.5, 4.9,
730                0.5, 1.7, 2.4, 3.5, 4.9, 5.3, 6.2, 7.8, 8.6, 9.3, 5.3, 6.2, 7.8, 8.6, 9.3, 0.5, 1.7, 2.4, 3.5, 4.9,
731                0.5, 1.7, 2.4, 3.5, 4.9, 5.3, 6.2, 7.8, 8.6, 9.3, 5.3, 6.2, 7.8, 8.6, 9.3, 0.5, 1.7, 2.4, 3.5, 4.9,
732                0.5, 1.7, 2.4, 3.5, 4.9, 5.3, 6.2, 7.8, 8.6, 9.3, 5.3, 6.2, 7.8, 8.6, 9.3, 0.5, 1.7, 2.4, 3.5, 4.9,
733            ]
734        );
735
736        Ok(())
737    }
738
739    #[test]
740    fn test_read_floats() -> Result<()> {
741        let data = read_file("fixtures/floats.usdc")?;
742
743        let single = data
744            .get_field(&sdf::path("/PrimF.single")?, "default")?
745            .into_owned()
746            .try_as_float()
747            .unwrap();
748        assert_eq!(single, 3.5);
749
750        let array = data
751            .get_field(&sdf::path("/PrimF.simple")?, "default")?
752            .into_owned()
753            .try_as_float_vec()
754            .unwrap();
755        assert_eq!(array, vec![9.1, 2.3, 6.4, 7.4, 3.6, 4.3, 5.3, 5.6, 8.7, 4.7]);
756
757        let compressed = data
758            .get_field(&sdf::path("/PrimF.copressed")?, "default")?
759            .into_owned()
760            .try_as_float_vec()
761            .unwrap();
762
763        assert_eq!(
764            compressed,
765            vec![
766                9.1, 2.3, 6.4, 7.4, 3.6, 4.3, 5.3, 5.6, 8.7, 4.7, 4.7, 9.1, 2.3, 6.4, 7.4, 3.6, 4.3, 5.3, 5.6, 8.7,
767                8.7, 4.7, 9.1, 2.3, 6.4, 7.4, 3.6, 4.3, 5.3, 5.6, 5.6, 8.7, 4.7, 9.1, 2.3, 6.4, 7.4, 3.6, 4.3, 5.3,
768                5.3, 5.6, 8.7, 4.7, 9.1, 2.3, 6.4, 7.4, 3.6, 4.3,
769            ]
770        );
771
772        Ok(())
773    }
774
775    #[test]
776    fn test_read_integer_compressed_float_array() -> Result<()> {
777        // OpenUSD serialises an all-integral float array with the `i` code:
778        // its LZ4 payload still needs Usd_IntegerCompression decoding.
779        let data = read_file("fixtures/integer_compressed_floats.usdc")?;
780        let weights = data
781            .get_field(&sdf::path("/IntegerCompressedFloats.weights")?, "default")?
782            .into_owned()
783            .try_as_float_vec()
784            .unwrap();
785
786        assert_eq!(
787            weights,
788            vec![1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0,]
789        );
790        Ok(())
791    }
792
793    #[test]
794    fn test_read_halfs() -> Result<()> {
795        let data = read_file("fixtures/floats.usdc")?;
796
797        let single = data
798            .get_field(&sdf::path("/PrimH.single")?, "default")?
799            .into_owned()
800            .try_as_half()
801            .unwrap();
802
803        assert_eq!(single, f16::from_f32(2.9));
804
805        let array = data
806            .get_field(&sdf::path("/PrimH.simple")?, "default")?
807            .into_owned()
808            .try_as_half_vec()
809            .unwrap();
810
811        assert_eq!(
812            array,
813            [4.3, 5.3, 5.6, 8.7, 4.7, 9.1, 2.3, 6.4, 7.4, 3.6]
814                .into_iter()
815                .map(f16::from_f32)
816                .collect::<Vec<_>>()
817        );
818
819        let compressed = data
820            .get_field(&sdf::path("/PrimH.copressed")?, "default")?
821            .into_owned()
822            .try_as_half_vec()
823            .unwrap();
824
825        assert_eq!(
826            compressed,
827            [
828                4.3, 5.3, 5.6, 8.7, 4.7, 9.1, 2.3, 6.4, 7.4, 3.6, 3.6, 4.3, 5.3, 5.6, 8.7, 4.7, 9.1, 2.3, 6.4, 7.4,
829                7.4, 3.6, 4.3, 5.3, 5.6, 8.7, 4.7, 9.1, 2.3, 6.4, 6.4, 7.4, 3.6, 4.3, 5.3, 5.6, 8.7, 4.7, 9.1, 2.3,
830                2.3, 6.4, 7.4, 3.6, 4.3, 5.3, 5.6, 8.7, 4.7, 9.1,
831            ]
832            .into_iter()
833            .map(f16::from_f32)
834            .collect::<Vec<_>>()
835        );
836
837        Ok(())
838    }
839
840    #[test]
841    fn test_read_time_series() -> Result<()> {
842        let data = read_file("fixtures/timesamples.usdc")?;
843
844        let samples = data
845            .get_field(&sdf::path("/Prim.prop")?, "timeSamples")?
846            .into_owned()
847            .try_as_time_samples()
848            .unwrap();
849        assert_eq!(samples.len(), 2);
850
851        let keys = samples.iter().map(|(d, _)| d).copied().collect::<Vec<_>>();
852        assert_eq!(keys, vec![4.0, 5.0]);
853
854        assert!(matches!(&samples[0].1, sdf::Value::Double(x) if *x == 40.0_f64));
855        assert!(matches!(samples[1].1, sdf::Value::ValueBlock));
856
857        Ok(())
858    }
859
860    #[test]
861    fn test_read_ints_i32() -> Result<()> {
862        let data = read_file("fixtures/ints.usdc")?;
863
864        assert_eq!(
865            data.get_field(&sdf::path("/Prim32.single")?, "default")?
866                .into_owned()
867                .try_as_int()
868                .unwrap(),
869            12938
870        );
871
872        assert_eq!(
873            data.get_field(&sdf::path("/Prim32.compressed")?, "default")?
874                .into_owned()
875                .try_as_int_vec()
876                .unwrap(),
877            vec![
878                1, 2, 4, 5, -3, 4, 5, -2, 3, -0, 3, 2, 4, -2, 4, 1, 8, -1, 5, -5, 2, 6, -3, 4, 6, 3, -7, 2, -3, 3, 6,
879                2, 6, 6, -4, 2, -4, 6, -2, 4
880            ]
881        );
882
883        Ok(())
884    }
885
886    #[test]
887    fn test_read_ints_i64() -> Result<()> {
888        let data = read_file("fixtures/ints.usdc")?;
889
890        assert_eq!(
891            data.get_field(&sdf::path("/Prim64.single")?, "default")?
892                .into_owned()
893                .try_as_int_64()
894                .unwrap(),
895            1234567890
896        );
897
898        assert_eq!(
899            data.get_field(&sdf::path("/Prim64.compressed")?, "default")?
900                .into_owned()
901                .try_as_int_64_vec()
902                .unwrap(),
903            vec![
904                10, 23, 48, 45, -23, 43, 65, -23, 23, -10, 34, 23, 45, -12, 34, 16, 18, -12, 65, -65, 21, 67, -43, 34,
905                36, 34, -67, 25, -23, 63, 65, 23, 65, 63, -54, 23, -44, 65, -62, 54
906            ]
907        );
908
909        Ok(())
910    }
911
912    #[test]
913    fn test_read_ints_u32() -> Result<()> {
914        let data = read_file("fixtures/ints.usdc")?;
915
916        assert_eq!(
917            data.get_field(&sdf::path("/PrimU32.single")?, "default")?
918                .into_owned()
919                .try_as_uint()
920                .unwrap(),
921            80129
922        );
923
924        assert_eq!(
925            data.get_field(&sdf::path("/PrimU32.compressed")?, "default")?
926                .into_owned()
927                .try_as_uint_vec()
928                .unwrap(),
929            vec![
930                1, 2, 4, 5, 3, 4, 5, 2, 3, 0, 3, 2, 4, 2, 4, 1, 8, 1, 5, 5, 2, 6, 3, 4, 6, 3, 7, 2, 3, 3, 6, 2, 6, 6,
931                4, 2, 4, 6, 2, 4
932            ]
933        );
934
935        Ok(())
936    }
937
938    #[test]
939    fn test_read_ints_u64() -> Result<()> {
940        let data = read_file("fixtures/ints.usdc")?;
941
942        assert_eq!(
943            data.get_field(&sdf::path("/PrimU64.single")?, "default")?
944                .into_owned()
945                .try_as_uint_64()
946                .unwrap(),
947            432423654
948        );
949
950        assert_eq!(
951            data.get_field(&sdf::path("/PrimU64.compressed")?, "default")?
952                .into_owned()
953                .try_as_uint_64_vec()
954                .unwrap(),
955            vec![
956                34, 23, 45, 12, 34, 16, 18, 12, 65, 65, 10, 23, 48, 45, 23, 43, 65, 23, 23, 10, 65, 23, 65, 63, 54, 23,
957                44, 65, 62, 54, 21, 67, 43, 34, 36, 34, 67, 25, 23, 63,
958            ]
959        );
960
961        Ok(())
962    }
963
964    #[test]
965    fn test_read_array_fields() -> Result<()> {
966        let data = read_file("fixtures/fields.usdc")?;
967
968        // defaultPrim = "World"
969        let default_prim = data.get_field(&sdf::Path::abs_root(), "defaultPrim")?;
970        assert_eq!(default_prim.try_as_token_ref().unwrap().as_str(), "World");
971
972        // float4[] clippingPlanes = []
973        let clipping_planes = data.get_field(&sdf::path("/World.clippingPlanes")?, "default")?;
974        assert!(clipping_planes.into_owned().try_as_vec_4f_vec().unwrap().is_empty());
975
976        // float2 clippingRange = (1, 10000000)
977        let clipping_range = data.get_field(&sdf::path("/World.clippingRange")?, "default")?;
978        assert_eq!(
979            clipping_range.into_owned().try_as_vec_2f().unwrap(),
980            gf::vec2f(1.0, 10000000.0)
981        );
982
983        // float3 diffuseColor = (0.18, 0.18, 0.18)
984        let diffuse_color = data.get_field(&sdf::path("/World.diffuseColor")?, "default")?;
985        assert_eq!(
986            diffuse_color.into_owned().try_as_vec_3f().unwrap(),
987            gf::vec3f(0.18, 0.18, 0.18)
988        );
989
990        // int[] faceVertexCounts = [1, 2, 3, 4, 5, 6]
991        let face_vertex_counts = data.get_field(&sdf::path("/World.faceVertexCounts")?, "default")?;
992        assert_eq!(
993            &face_vertex_counts.into_owned().try_as_int_vec().unwrap(),
994            &[1, 2, 3, 4, 5, 6]
995        );
996
997        // normal3f[] normals = [(0, 1, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), (0, 1, 0), (0, 0, 1), (1, 0, 0)]
998        let normals = data.get_field(&sdf::path("/World.normals")?, "default")?;
999        assert_eq!(
1000            normals.try_as_vec_3f_vec_ref().unwrap(),
1001            &[
1002                gf::vec3f(0.0, 1.0, 0.0),
1003                gf::vec3f(1.0, 0.0, 0.0),
1004                gf::vec3f(0.0, 1.0, 0.0),
1005                gf::vec3f(0.0, 0.0, 1.0),
1006                gf::vec3f(0.0, 1.0, 0.0),
1007                gf::vec3f(0.0, 0.0, 1.0),
1008                gf::vec3f(1.0, 0.0, 0.0),
1009            ]
1010        );
1011
1012        // double3 xformOp:rotateXYZ = (0, 0, 0)
1013        let xform_op_rotate_xyz = data.get_field(&sdf::path("/World.xformOp:rotateXYZ")?, "default")?;
1014        assert_eq!(
1015            *xform_op_rotate_xyz.try_as_vec_3d_ref().unwrap(),
1016            gf::vec3d(0.0, 0.0, 0.0)
1017        );
1018
1019        // double3 xformOp:scale = (1, 1, 1)
1020        let xform_op_scale = data.get_field(&sdf::path("/World.xformOp:scale")?, "default")?;
1021        assert_eq!(*xform_op_scale.try_as_vec_3d_ref().unwrap(), gf::vec3d(1.0, 1.0, 1.0));
1022
1023        // double3 xformOp:translate = (0, 1, 0)
1024        let xform_op_translate = data.get_field(&sdf::path("/World.xformOp:translate")?, "default")?;
1025        assert_eq!(
1026            *xform_op_translate.try_as_vec_3d_ref().unwrap(),
1027            gf::vec3d(0.0, 1.0, 0.0)
1028        );
1029
1030        Ok(())
1031    }
1032
1033    #[test]
1034    fn test_read_time_code() -> Result<()> {
1035        let data = read_file("fixtures/sdf_types.usdc")?;
1036
1037        let time_code = data
1038            .get_field(&sdf::path("/World.timeCodeValue")?, "default")?
1039            .into_owned()
1040            .try_as_time_code()
1041            .unwrap();
1042        assert_eq!(time_code, sdf::TimeCode(24.0));
1043
1044        let time_code_array = data
1045            .get_field(&sdf::path("/World.timeCodeArray")?, "default")?
1046            .into_owned()
1047            .try_as_time_code_vec()
1048            .unwrap();
1049        assert_eq!(
1050            time_code_array,
1051            vec![sdf::TimeCode(1.0), sdf::TimeCode(12.0), sdf::TimeCode(24.0)]
1052        );
1053
1054        Ok(())
1055    }
1056
1057    #[test]
1058    fn test_read_target_paths() -> Result<()> {
1059        let data = read_file("fixtures/sdf_types.usdc")?;
1060
1061        let targets = data
1062            .get_field(&sdf::path("/World.targets")?, "targetPaths")?
1063            .into_owned()
1064            .try_as_path_list_op()
1065            .unwrap();
1066
1067        assert!(targets.explicit);
1068        assert_eq!(
1069            targets.explicit_items,
1070            vec![sdf::path("/World/ChildA")?, sdf::path("/World/ChildB")?]
1071        );
1072
1073        Ok(())
1074    }
1075
1076    /// String arrays should have a readable default value.
1077    #[test]
1078    fn test_read_string_array_default() -> Result<()> {
1079        let data = read_file(
1080            "vendor/core-spec-supplemental-release_dec2025/file_formats/tests/assets/binary/gen_string.usdc",
1081        )?;
1082
1083        let array = data
1084            .get_field(&sdf::path("/root.array")?, "default")?
1085            .into_owned()
1086            .try_as_string_vec()
1087            .unwrap();
1088
1089        assert_eq!(array, vec!["Hello/World", "Good/Bye"]);
1090
1091        Ok(())
1092    }
1093
1094    /// gf::Vec2h single value should read half-floats, not raw integers.
1095    #[test]
1096    fn test_read_vec2h_single() -> Result<()> {
1097        let data =
1098            read_file("vendor/core-spec-supplemental-release_dec2025/file_formats/tests/assets/binary/gen_vec2h.usdc")?;
1099
1100        let single = data
1101            .get_field(&sdf::path("/root.single")?, "default")?
1102            .into_owned()
1103            .try_as_vec_2h()
1104            .unwrap();
1105
1106        #[allow(clippy::approx_constant)]
1107        let expected_x = f16::from_f32(3.14);
1108        assert_eq!(single[0], expected_x);
1109        assert_eq!(single[1], f16::from_f32(4.824));
1110
1111        // Inlined value should also read correctly.
1112        let inlined = data
1113            .get_field(&sdf::path("/root.inlined")?, "default")?
1114            .into_owned()
1115            .try_as_vec_2h()
1116            .unwrap();
1117
1118        assert_eq!(inlined[0], f16::from_f32(0.0));
1119        assert_eq!(inlined[1], f16::from_f32(1.0));
1120
1121        Ok(())
1122    }
1123}