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::{bail, Result};
6use layout::ValueRep;
7
8mod coding;
9mod layout;
10mod reader;
11
12pub use layout::{version, Version};
13pub use reader::{CrateFile, ReadExt};
14
15use crate::sdf;
16
17/// USDC binary format magic bytes (`PXR-USDC`).
18pub const MAGIC: &[u8] = b"PXR-USDC";
19
20#[derive(Default, Debug)]
21struct Spec {
22    /// Specifies the type of an object.
23    ty: sdf::SpecType,
24    /// Spec properties.
25    fields: HashMap<String, ValueRep>,
26}
27
28/// High level interface to binary data.
29pub struct CrateData<R> {
30    file: RefCell<CrateFile<R>>,
31    data: HashMap<sdf::Path, Spec>,
32}
33
34impl<R> CrateData<R>
35where
36    R: io::Read + io::Seek,
37{
38    /// Read binary data from any reader.
39    pub fn open(reader: R, safe: bool) -> Result<Self> {
40        let mut file = CrateFile::open(reader)?;
41
42        if safe {
43            file.validate()?;
44        }
45
46        // Build crate data
47
48        let mut data = HashMap::default();
49        let specs = mem::take(&mut file.specs);
50
51        for filespec in specs {
52            let path = file.paths[filespec.path_index].clone();
53            let ty = filespec.spec_type;
54
55            let mut fields = HashMap::default();
56            let mut index = filespec.fieldset_index;
57
58            while index < file.fieldsets.len() {
59                let current = match file.fieldsets[index] {
60                    Some(value) => value,
61                    None => break,
62                };
63
64                index += 1;
65
66                let field = &file.fields[current];
67                let name = file.tokens[field.token_index].clone();
68
69                fields.insert(name, field.value_rep);
70            }
71
72            data.insert(path, Spec { ty, fields });
73        }
74
75        Ok(Self {
76            file: RefCell::new(file),
77            data,
78        })
79    }
80}
81
82impl<R> sdf::AbstractData for CrateData<R>
83where
84    R: io::Read + io::Seek,
85{
86    #[inline]
87    fn has_spec(&self, path: &sdf::Path) -> bool {
88        self.data.contains_key(path)
89    }
90
91    #[inline]
92    fn has_field(&self, path: &sdf::Path, field: &str) -> bool {
93        self.data.get(path).is_some_and(|spec| spec.fields.contains_key(field))
94    }
95
96    #[inline]
97    fn spec_type(&self, path: &sdf::Path) -> Option<sdf::SpecType> {
98        self.data.get(path).map(|spec| spec.ty)
99    }
100
101    fn get(&self, path: &sdf::Path, field: &str) -> Result<Cow<'_, sdf::Value>> {
102        let Some(spec) = self.data.get(path) else {
103            bail!("No spec found for path: {path}")
104        };
105
106        let Some(value_rep) = spec.fields.get(field).cloned() else {
107            bail!("No field found for path '{path}' and field '{field}'")
108        };
109
110        let value = self.file.borrow_mut().value(value_rep)?;
111
112        Ok(Cow::Owned(value))
113    }
114
115    #[inline]
116    fn list(&self, path: &sdf::Path) -> Option<Vec<String>> {
117        self.data.get(path).map(|spec| spec.fields.keys().cloned().collect())
118    }
119}
120
121/// Read `usdc` data from a file on disk.
122pub fn read_file(path: impl AsRef<Path>) -> Result<Box<dyn sdf::AbstractData>> {
123    let file = std::fs::File::open(path)?;
124    let data = CrateData::open(file, true)?;
125
126    Ok(Box::new(data))
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use half::f16;
133    use std::path::Path;
134
135    #[test]
136    fn test_crate_hierarchy() -> Result<()> {
137        let path = Path::new("./vendor/usd-wg-assets/full_assets/ElephantWithMonochord/SoC-ElephantWithMonochord.usdc");
138        if !path.exists() {
139            eprintln!(
140                "Skipping test_crate_hierarchy: fixture not available at {}",
141                path.display()
142            );
143            return Ok(());
144        }
145
146        let data = read_file(path)?;
147
148        let prim_children: Vec<String> = data
149            .get(&sdf::Path::abs_root(), "primChildren")?
150            .into_owned()
151            .try_as_token_vec()
152            .unwrap();
153        assert_eq!(prim_children, vec!["SoC_ElephantWithMonochord".to_string()]);
154
155        let elephant: Vec<String> = data
156            .get(&sdf::path("/SoC_ElephantWithMonochord")?, "primChildren")?
157            .into_owned()
158            .try_as_token_vec()
159            .unwrap();
160
161        assert_eq!(
162            elephant,
163            vec![
164                "Materials".to_string(),
165                "Object".to_string(),
166                "CharacterAudioSource".to_string()
167            ]
168        );
169
170        let materials: Vec<String> = data
171            .get(&sdf::path("/SoC_ElephantWithMonochord/Materials")?, "primChildren")?
172            .into_owned()
173            .try_as_token_vec()
174            .unwrap();
175
176        assert_eq!(
177            materials,
178            vec!["Elefant_Mat_68050".to_string(), "Monochord_Mat_68062".to_string()]
179        );
180
181        Ok(())
182    }
183
184    #[test]
185    fn test_read_custom_layer_data() {
186        let data = read_file("fixtures/fields.usdc").unwrap();
187
188        let custom_layer_data = data.get(&sdf::Path::abs_root(), "customLayerData").unwrap();
189
190        // customLayerData = {
191        //  string test = "Test string"
192        // }
193        let copyright = custom_layer_data
194            .try_as_dictionary_ref()
195            .unwrap()
196            .get("test")
197            .unwrap()
198            .try_as_string_ref()
199            .unwrap();
200
201        assert_eq!(copyright, "Test string");
202    }
203
204    #[test]
205    fn test_read_bool() -> Result<()> {
206        let data = read_file("fixtures/fields.usdc")?;
207
208        let single = data
209            .get(&sdf::path("/World.flipNormals")?, "default")?
210            .into_owned()
211            .try_as_bool()
212            .unwrap();
213
214        assert!(single);
215
216        let bool_array = data
217            .get(&sdf::path("/World.boolArray")?, "default")?
218            .into_owned()
219            .try_as_bool_vec()
220            .unwrap();
221
222        assert_eq!(bool_array, vec![true, true, false, false, true, false]);
223
224        Ok(())
225    }
226
227    #[test]
228    fn test_read_chars() -> Result<()> {
229        let data = read_file("fixtures/fields.usdc")?;
230
231        let single_char = data
232            .get(&sdf::path("/World.singleChar")?, "default")?
233            .into_owned()
234            .try_as_uchar()
235            .unwrap();
236
237        assert_eq!(single_char, 128);
238
239        let char_array = data
240            .get(&sdf::path("/World.chars")?, "default")?
241            .into_owned()
242            .try_as_uchar_vec()
243            .unwrap();
244
245        assert_eq!(char_array, vec![128, 129, 130, 131, 132, 133, 134, 135, 136, 137]);
246
247        Ok(())
248    }
249
250    #[test]
251    fn test_read_quat_floats() -> Result<()> {
252        let data = read_file("fixtures/fields.usdc")?;
253
254        let quat = data
255            .get(&sdf::path("/World.quatfSingle")?, "default")?
256            .into_owned()
257            .try_as_quatf()
258            .unwrap();
259
260        assert_eq!(quat, [2.9, 8.5, 4.6, 1.4]);
261
262        let quat = data
263            .get(&sdf::path("/World.quatfArr")?, "default")?
264            .into_owned()
265            .try_as_quatf_vec()
266            .unwrap();
267
268        assert_eq!(
269            quat,
270            vec![
271                [3.5, 2.6, 3.6, 4.2], // 1
272                [5.3, 6.3, 5.2, 2.4], // 2
273                [4.3, 2.4, 6.4, 7.1], // 3
274            ]
275        );
276
277        Ok(())
278    }
279
280    #[test]
281    fn test_read_quat_doubles() -> Result<()> {
282        let data = read_file("fixtures/fields.usdc")?;
283
284        let quat = data
285            .get(&sdf::path("/World.quatdSingle")?, "default")?
286            .into_owned()
287            .try_as_quatd()
288            .unwrap();
289
290        assert_eq!(quat, [5.3, 6.3, 5.2, 2.4]);
291
292        let quat = data
293            .get(&sdf::path("/World.quatdArr")?, "default")?
294            .into_owned()
295            .try_as_quatd_vec()
296            .unwrap();
297
298        assert_eq!(
299            quat,
300            vec![
301                [3.5, 2.6, 3.6, 4.2], // 1
302                [4.3, 2.4, 6.4, 7.1], // 2
303            ]
304        );
305
306        Ok(())
307    }
308
309    #[test]
310    fn test_read_quat_half() -> Result<()> {
311        let data = read_file("fixtures/fields.usdc")?;
312
313        let quat = data
314            .get(&sdf::path("/World.quathSingle")?, "default")?
315            .into_owned()
316            .try_as_quath()
317            .unwrap();
318
319        assert_eq!(quat, [4.6, 2.5, 7.6, 3.5].map(f16::from_f32));
320
321        let quat = data
322            .get(&sdf::path("/World.quathArr")?, "default")?
323            .into_owned()
324            .try_as_quath_vec()
325            .unwrap();
326
327        assert_eq!(
328            quat,
329            vec![
330                [2.4, 7.8, 8.5, 4.7].map(f16::from_f32), // 1
331                [6.7, 5.6, 5.3, 4.6].map(f16::from_f32), // 2
332            ]
333        );
334
335        Ok(())
336    }
337
338    #[test]
339    fn test_read_sub_layers() -> Result<()> {
340        let data = read_file("fixtures/expressions.usdc")?;
341
342        let sub_layer_offsets = data
343            .get(&sdf::path("/")?, "subLayerOffsets")?
344            .into_owned()
345            .try_as_layer_offset_vec()
346            .unwrap()
347            .into_iter()
348            .next()
349            .unwrap();
350
351        assert_eq!(sub_layer_offsets.offset, 0.0);
352        assert_eq!(sub_layer_offsets.scale, 1.0);
353
354        let sub_layers = data
355            .get(&sdf::path("/")?, "subLayers")?
356            .into_owned()
357            .try_as_string_vec()
358            .unwrap();
359        assert_eq!(sub_layers, vec!["`\"render_pass_${RENDER_PASS}.usd\"`"]);
360
361        Ok(())
362    }
363
364    #[test]
365    fn test_read_variant_selection() -> Result<()> {
366        let data = read_file("fixtures/expressions.usdc")?;
367
368        // prepend variantSets = "displayVariantSet"
369        let variant_set_names = data
370            .get(&sdf::path("/asset1")?, "variantSetNames")?
371            .into_owned()
372            .try_as_string_list_op()
373            .unwrap();
374        assert_eq!(variant_set_names.prepended_items, vec!["displayVariantSet".to_string()]);
375
376        let variant_selection = data
377            .get(&sdf::path("/asset1")?, "variantSelection")?
378            .into_owned()
379            .try_as_variant_selection_map()
380            .unwrap();
381
382        assert_eq!(variant_selection.len(), 1);
383        assert_eq!(
384            variant_selection.get("displayVariantSet").unwrap(),
385            "`${VARIANT_CHOICE}`"
386        );
387
388        Ok(())
389    }
390
391    #[test]
392    fn test_read_connection() -> Result<()> {
393        let data = read_file("fixtures/connection.usdc")?;
394
395        let conn = data
396            .get(&sdf::path("/boardMat/stReader.inputs:varname")?, "connectionPaths")?
397            .into_owned()
398            .try_as_path_list_op()
399            .unwrap();
400
401        assert!(conn.explicit);
402        assert_eq!(
403            conn.explicit_items,
404            vec![sdf::path("/TexModel/boardMat.inputs:frame:stPrimvarName")?]
405        );
406
407        let conn = data
408            .get(&sdf::path("/boardMat.outputs:surface")?, "connectionPaths")?
409            .into_owned()
410            .try_as_path_list_op()
411            .unwrap();
412
413        assert!(conn.explicit);
414        assert_eq!(
415            conn.explicit_items,
416            vec![sdf::path("/TexModel/boardMat/PBRShader.outputs:surface")?]
417        );
418
419        Ok(())
420    }
421
422    #[test]
423    fn test_read_reference() -> Result<()> {
424        let data = read_file("fixtures/reference.usdc")?;
425
426        let references = data
427            .get(&sdf::path("/MarbleCollection/Marble_Red")?, "references")?
428            .into_owned()
429            .try_as_reference_list_op()
430            .unwrap();
431
432        assert!(references.appended_items.is_empty());
433        assert!(references.deleted_items.is_empty());
434        assert!(references.ordered_items.is_empty());
435
436        assert!(references.explicit);
437        assert_eq!(references.explicit_items.len(), 1);
438
439        assert_eq!(references.explicit_items[0].asset_path, "Marble.usd");
440        assert_eq!(references.explicit_items[0].prim_path, sdf::path("/Foo/Bar")?);
441
442        Ok(())
443    }
444
445    #[test]
446    fn test_read_payload() -> Result<()> {
447        let data = read_file("fixtures/payload.usdc")?;
448
449        let payload = data
450            .get(&sdf::path("/MySphere1")?, "payload")?
451            .into_owned()
452            .try_as_payload()
453            .unwrap();
454
455        assert_eq!(payload.asset_path, "./payload.usda");
456        assert_eq!(payload.prim_path, sdf::path("/MySphere")?);
457
458        assert!(payload.layer_offset.is_some());
459
460        let layer_offset = payload.layer_offset.unwrap();
461        assert_eq!(layer_offset.offset, 0.0);
462        assert_eq!(layer_offset.scale, 1.0);
463
464        let payload_list_op = data
465            .get(&sdf::path("/MySphere2")?, "payload")?
466            .into_owned()
467            .try_as_payload_list_op()
468            .unwrap();
469
470        assert!(!payload_list_op.explicit);
471
472        assert!(payload_list_op.explicit_items.is_empty());
473        assert!(payload_list_op.added_items.is_empty());
474        assert!(payload_list_op.appended_items.is_empty());
475        assert!(payload_list_op.deleted_items.is_empty());
476        assert!(payload_list_op.ordered_items.is_empty());
477
478        assert_eq!(payload_list_op.prepended_items.len(), 1);
479        assert_eq!(payload_list_op.prepended_items[0].asset_path, "./cube_payload.usda");
480        assert_eq!(payload_list_op.prepended_items[0].prim_path, sdf::path("/PayloadCube")?);
481
482        Ok(())
483    }
484
485    #[test]
486    fn test_read_doubles() -> Result<()> {
487        let data = read_file("fixtures/floats.usdc")?;
488
489        let single = data
490            .get(&sdf::path("/PrimD.single")?, "default")?
491            .into_owned()
492            .try_as_double()
493            .unwrap();
494        assert_eq!(single, 4.3_f64);
495
496        let array = data
497            .get(&sdf::path("/PrimD.simple")?, "default")?
498            .into_owned()
499            .try_as_double_vec()
500            .unwrap();
501        assert_eq!(array, vec![0.5, 1.7, 2.4, 3.5, 4.9, 5.3, 6.2, 7.8, 8.6, 9.3]);
502
503        let compressed = data
504            .get(&sdf::path("/PrimD.copressed")?, "default")?
505            .into_owned()
506            .try_as_double_vec()
507            .unwrap();
508        assert_eq!(
509            compressed,
510            vec![
511                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,
512                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,
513                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,
514                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,
515                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,
516            ]
517        );
518
519        Ok(())
520    }
521
522    #[test]
523    fn test_read_floats() -> Result<()> {
524        let data = read_file("fixtures/floats.usdc")?;
525
526        let single = data
527            .get(&sdf::path("/PrimF.single")?, "default")?
528            .into_owned()
529            .try_as_float()
530            .unwrap();
531        assert_eq!(single, 3.5);
532
533        let array = data
534            .get(&sdf::path("/PrimF.simple")?, "default")?
535            .into_owned()
536            .try_as_float_vec()
537            .unwrap();
538        assert_eq!(array, vec![9.1, 2.3, 6.4, 7.4, 3.6, 4.3, 5.3, 5.6, 8.7, 4.7]);
539
540        let compressed = data
541            .get(&sdf::path("/PrimF.copressed")?, "default")?
542            .into_owned()
543            .try_as_float_vec()
544            .unwrap();
545
546        assert_eq!(
547            compressed,
548            vec![
549                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,
550                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,
551                5.3, 5.6, 8.7, 4.7, 9.1, 2.3, 6.4, 7.4, 3.6, 4.3,
552            ]
553        );
554
555        Ok(())
556    }
557
558    #[test]
559    fn test_read_halfs() -> Result<()> {
560        let data = read_file("fixtures/floats.usdc")?;
561
562        let single = data
563            .get(&sdf::path("/PrimH.single")?, "default")?
564            .into_owned()
565            .try_as_half()
566            .unwrap();
567
568        assert_eq!(single, f16::from_f32(2.9));
569
570        let array = data
571            .get(&sdf::path("/PrimH.simple")?, "default")?
572            .into_owned()
573            .try_as_half_vec()
574            .unwrap();
575
576        assert_eq!(
577            array,
578            [4.3, 5.3, 5.6, 8.7, 4.7, 9.1, 2.3, 6.4, 7.4, 3.6]
579                .into_iter()
580                .map(f16::from_f32)
581                .collect::<Vec<_>>()
582        );
583
584        let compressed = data
585            .get(&sdf::path("/PrimH.copressed")?, "default")?
586            .into_owned()
587            .try_as_half_vec()
588            .unwrap();
589
590        assert_eq!(
591            compressed,
592            [
593                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,
594                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,
595                2.3, 6.4, 7.4, 3.6, 4.3, 5.3, 5.6, 8.7, 4.7, 9.1,
596            ]
597            .into_iter()
598            .map(f16::from_f32)
599            .collect::<Vec<_>>()
600        );
601
602        Ok(())
603    }
604
605    #[test]
606    fn test_read_time_series() -> Result<()> {
607        let data = read_file("fixtures/timesamples.usdc")?;
608
609        let samples = data
610            .get(&sdf::path("/Prim.prop")?, "timeSamples")?
611            .into_owned()
612            .try_as_time_samples()
613            .unwrap();
614        assert_eq!(samples.len(), 2);
615
616        let keys = samples.iter().map(|(d, _)| d).copied().collect::<Vec<_>>();
617        assert_eq!(keys, vec![4.0, 5.0]);
618
619        assert!(matches!(&samples[0].1, sdf::Value::Double(x) if *x == 40.0_f64));
620        assert!(matches!(samples[1].1, sdf::Value::ValueBlock));
621
622        Ok(())
623    }
624
625    #[test]
626    fn test_read_ints_i32() -> Result<()> {
627        let data = read_file("fixtures/ints.usdc")?;
628
629        assert_eq!(
630            data.get(&sdf::path("/Prim32.single")?, "default")?
631                .into_owned()
632                .try_as_int()
633                .unwrap(),
634            12938
635        );
636
637        assert_eq!(
638            data.get(&sdf::path("/Prim32.compressed")?, "default")?
639                .into_owned()
640                .try_as_int_vec()
641                .unwrap(),
642            vec![
643                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,
644                2, 6, 6, -4, 2, -4, 6, -2, 4
645            ]
646        );
647
648        Ok(())
649    }
650
651    #[test]
652    fn test_read_ints_i64() -> Result<()> {
653        let data = read_file("fixtures/ints.usdc")?;
654
655        assert_eq!(
656            data.get(&sdf::path("/Prim64.single")?, "default")?
657                .into_owned()
658                .try_as_int_64()
659                .unwrap(),
660            1234567890
661        );
662
663        assert_eq!(
664            data.get(&sdf::path("/Prim64.compressed")?, "default")?
665                .into_owned()
666                .try_as_int_64_vec()
667                .unwrap(),
668            vec![
669                10, 23, 48, 45, -23, 43, 65, -23, 23, -10, 34, 23, 45, -12, 34, 16, 18, -12, 65, -65, 21, 67, -43, 34,
670                36, 34, -67, 25, -23, 63, 65, 23, 65, 63, -54, 23, -44, 65, -62, 54
671            ]
672        );
673
674        Ok(())
675    }
676
677    #[test]
678    fn test_read_ints_u32() -> Result<()> {
679        let data = read_file("fixtures/ints.usdc")?;
680
681        assert_eq!(
682            data.get(&sdf::path("/PrimU32.single")?, "default")?
683                .into_owned()
684                .try_as_uint()
685                .unwrap(),
686            80129
687        );
688
689        assert_eq!(
690            data.get(&sdf::path("/PrimU32.compressed")?, "default")?
691                .into_owned()
692                .try_as_uint_vec()
693                .unwrap(),
694            vec![
695                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,
696                4, 2, 4, 6, 2, 4
697            ]
698        );
699
700        Ok(())
701    }
702
703    #[test]
704    fn test_read_ints_u64() -> Result<()> {
705        let data = read_file("fixtures/ints.usdc")?;
706
707        assert_eq!(
708            data.get(&sdf::path("/PrimU64.single")?, "default")?
709                .into_owned()
710                .try_as_uint_64()
711                .unwrap(),
712            432423654
713        );
714
715        assert_eq!(
716            data.get(&sdf::path("/PrimU64.compressed")?, "default")?
717                .into_owned()
718                .try_as_uint_64_vec()
719                .unwrap(),
720            vec![
721                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,
722                44, 65, 62, 54, 21, 67, 43, 34, 36, 34, 67, 25, 23, 63,
723            ]
724        );
725
726        Ok(())
727    }
728
729    #[test]
730    fn test_read_array_fields() -> Result<()> {
731        let data = read_file("fixtures/fields.usdc")?;
732
733        // defaultPrim = "World"
734        let default_prim = data.get(&sdf::Path::abs_root(), "defaultPrim")?;
735        assert_eq!(default_prim.try_as_token_ref().unwrap(), "World");
736
737        // float4[] clippingPlanes = []
738        let clipping_planes = data.get(&sdf::path("/World.clippingPlanes")?, "default")?;
739        assert!(clipping_planes.into_owned().try_as_vec_4f_vec().unwrap().is_empty());
740
741        // float2 clippingRange = (1, 10000000)
742        let clipping_range = data.get(&sdf::path("/World.clippingRange")?, "default")?;
743        assert_eq!(clipping_range.into_owned().try_as_vec_2f().unwrap(), [1.0, 10000000.0]);
744
745        // float3 diffuseColor = (0.18, 0.18, 0.18)
746        let diffuse_color = data.get(&sdf::path("/World.diffuseColor")?, "default")?;
747        assert_eq!(diffuse_color.into_owned().try_as_vec_3f().unwrap(), [0.18, 0.18, 0.18]);
748
749        // int[] faceVertexCounts = [1, 2, 3, 4, 5, 6]
750        let face_vertex_counts = data.get(&sdf::path("/World.faceVertexCounts")?, "default")?;
751        assert_eq!(
752            &face_vertex_counts.into_owned().try_as_int_vec().unwrap(),
753            &[1, 2, 3, 4, 5, 6]
754        );
755
756        // normal3f[] normals = [(0, 1, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), (0, 1, 0), (0, 0, 1), (1, 0, 0)]
757        let normals = data.get(&sdf::path("/World.normals")?, "default")?;
758        assert_eq!(
759            normals.try_as_vec_3f_vec_ref().unwrap(),
760            &[
761                [0.0, 1.0, 0.0],
762                [1.0, 0.0, 0.0],
763                [0.0, 1.0, 0.0],
764                [0.0, 0.0, 1.0],
765                [0.0, 1.0, 0.0],
766                [0.0, 0.0, 1.0],
767                [1.0, 0.0, 0.0],
768            ]
769        );
770
771        // double3 xformOp:rotateXYZ = (0, 0, 0)
772        let xform_op_rotate_xyz = data.get(&sdf::path("/World.xformOp:rotateXYZ")?, "default")?;
773        assert_eq!(xform_op_rotate_xyz.try_as_vec_3d_ref().unwrap(), &[0.0, 0.0, 0.0]);
774
775        // double3 xformOp:scale = (1, 1, 1)
776        let xform_op_scale = data.get(&sdf::path("/World.xformOp:scale")?, "default")?;
777        assert_eq!(xform_op_scale.try_as_vec_3d_ref().unwrap(), &[1.0, 1.0, 1.0]);
778
779        // double3 xformOp:translate = (0, 1, 0)
780        let xform_op_translate = data.get(&sdf::path("/World.xformOp:translate")?, "default")?;
781        assert_eq!(xform_op_translate.try_as_vec_3d_ref().unwrap(), &[0.0, 1.0, 0.0]);
782
783        Ok(())
784    }
785
786    #[test]
787    fn test_read_time_code() -> Result<()> {
788        let data = read_file("fixtures/sdf_types.usdc")?;
789
790        let time_code = data
791            .get(&sdf::path("/World.timeCodeValue")?, "default")?
792            .into_owned()
793            .try_as_time_code()
794            .unwrap();
795        assert_eq!(time_code, 24.0);
796
797        let time_code_array = data
798            .get(&sdf::path("/World.timeCodeArray")?, "default")?
799            .into_owned()
800            .try_as_time_code_vec()
801            .unwrap();
802        assert_eq!(time_code_array, vec![1.0, 12.0, 24.0]);
803
804        Ok(())
805    }
806
807    #[test]
808    fn test_read_target_paths() -> Result<()> {
809        let data = read_file("fixtures/sdf_types.usdc")?;
810
811        let targets = data
812            .get(&sdf::path("/World.targets")?, "targetPaths")?
813            .into_owned()
814            .try_as_path_list_op()
815            .unwrap();
816
817        assert!(targets.explicit);
818        assert_eq!(
819            targets.explicit_items,
820            vec![sdf::path("/World/ChildA")?, sdf::path("/World/ChildB")?]
821        );
822
823        Ok(())
824    }
825
826    /// String arrays should have a readable default value.
827    #[test]
828    fn test_read_string_array_default() -> Result<()> {
829        let data = read_file(
830            "vendor/core-spec-supplemental-release_dec2025/file_formats/tests/assets/binary/gen_string.usdc",
831        )?;
832
833        let array = data
834            .get(&sdf::path("/root.array")?, "default")?
835            .into_owned()
836            .try_as_string_vec()
837            .unwrap();
838
839        assert_eq!(array, vec!["Hello/World", "Good/Bye"]);
840
841        Ok(())
842    }
843
844    /// Vec2h single value should read half-floats, not raw integers.
845    #[test]
846    fn test_read_vec2h_single() -> Result<()> {
847        let data =
848            read_file("vendor/core-spec-supplemental-release_dec2025/file_formats/tests/assets/binary/gen_vec2h.usdc")?;
849
850        let single = data
851            .get(&sdf::path("/root.single")?, "default")?
852            .into_owned()
853            .try_as_vec_2h()
854            .unwrap();
855
856        #[allow(clippy::approx_constant)]
857        let expected_x = f16::from_f32(3.14);
858        assert_eq!(single[0], expected_x);
859        assert_eq!(single[1], f16::from_f32(4.824));
860
861        // Inlined value should also read correctly.
862        let inlined = data
863            .get(&sdf::path("/root.inlined")?, "default")?
864            .into_owned()
865            .try_as_vec_2h()
866            .unwrap();
867
868        assert_eq!(inlined[0], f16::from_f32(0.0));
869        assert_eq!(inlined[1], f16::from_f32(1.0));
870
871        Ok(())
872    }
873}