1use 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
19pub const MAGIC: &[u8] = b"PXR-USDC";
21
22#[derive(Debug)]
30struct Spec {
31 ty: sdf::SpecType,
33 fieldset: Option<usize>,
36 authored: Vec<(String, Option<sdf::Value>)>,
40}
41
42impl Spec {
43 fn authored(&self, field: &str) -> Option<&Option<sdf::Value>> {
46 self.authored.iter().find(|(k, _)| k == field).map(|(_, v)| v)
47 }
48
49 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 fn remove_authored(&mut self, field: &str) {
61 self.authored.retain(|(k, _)| k != field);
62 }
63}
64
65pub 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 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 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 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 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 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
227fn 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
242fn 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
254pub 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
262pub 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
291const 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 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 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 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 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), gf::quatf(2.4, 5.3, 6.3, 5.2), gf::quatf(7.1, 4.3, 2.4, 6.4), ]
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 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), gf::quatd(7.1, 4.3, 2.4, 6.4), ]
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 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 ), 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 ), ]
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 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 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 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 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 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 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 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 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 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 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 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 #[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 #[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 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}