1use std::collections::HashMap;
44use std::fmt;
45use std::io::{self, Cursor, Write};
46use std::path::Path as FsPath;
47use std::rc::Rc;
48
49use crate::{ar, sdf, tf};
50
51use super::schema::FieldKey;
52use super::{
53 AbstractData, AttributeSpecMut, AttributeSpecRef, ChangeList, CowData, Data, DataError, FormatError, IntoPath,
54 LayerData, Patch, Path, PathParseError, PrimSpecMut, PrimSpecRef, PseudoRootSpecMut, PseudoRootSpecRef,
55 RelationshipSpecMut, RelationshipSpecRef, RelocateList, SpecError, SpecType, Value, ValueTypeError, sink,
56 try_into_path,
57};
58
59pub type LayerSinkId = sink::Id<dyn LayerSink>;
61
62const ANONYMOUS_PREFIX: &str = "anon:";
65
66pub struct Layer {
68 pub identifier: String,
70 real_path: Option<String>,
82 data: CowData<LayerData>,
90 changes: ChangeList,
94 sinks: sink::Set<dyn LayerSink>,
98 resolver: Option<Rc<dyn ar::Resolver>>,
101}
102
103impl Layer {
104 pub(crate) fn new(identifier: impl Into<String>, data: LayerData) -> Self {
113 Self::build(identifier.into(), None, data)
114 }
115
116 pub fn open(identifier: impl AsRef<str>) -> crate::Result<Self> {
123 Self::open_with(ar::DefaultResolver::new(), identifier)
124 }
125
126 pub fn open_with(resolver: impl ar::Resolver + 'static, identifier: impl AsRef<str>) -> crate::Result<Self> {
130 let registry = sdf::LayerRegistry::new(Box::new(resolver));
131 let identifier = registry.create_identifier(identifier.as_ref(), None);
135 let layer = registry.open_layer(&identifier)?;
136 layer.ok_or(crate::Error::UnresolvedAsset(identifier))
137 }
138
139 pub fn from_bytes(tag: impl fmt::Display, bytes: impl Into<ar::AssetBuffer>) -> Result<Self, FormatError> {
150 let identifier = Self::anonymous_identifier(tag);
151 let data = sdf::LayerRegistry::read_bytes(bytes.into(), &identifier)?;
152 Ok(Self::new(identifier, data))
153 }
154
155 pub(crate) fn new_resolved(
165 identifier: impl Into<String>,
166 real_path: &ar::ResolvedPath,
167 data: LayerData,
168 resolver: Rc<dyn ar::Resolver>,
169 ) -> Self {
170 let identifier = identifier.into();
171 let real_path = real_path.to_string();
172 let real_path = (real_path != identifier).then_some(real_path);
173 let mut layer = Self::build(identifier, real_path, data);
174 layer.resolver = Some(resolver);
175 layer
176 }
177
178 fn build(identifier: String, real_path: Option<String>, data: LayerData) -> Self {
181 Self {
182 identifier,
183 real_path,
184 data: CowData::new(data),
185 changes: ChangeList::new(),
186 sinks: sink::Set::default(),
187 resolver: None,
188 }
189 }
190
191 pub fn add_sink<S: LayerSink + 'static>(&mut self, sink: S) -> LayerSinkId {
198 self.sinks.add(Box::new(sink))
199 }
200
201 pub fn remove_sink(&mut self, id: LayerSinkId) {
203 self.sinks.remove(id);
204 }
205
206 pub fn data(&self) -> &dyn AbstractData {
209 &self.data
210 }
211
212 fn rollback(&mut self) {
218 self.data.rollback();
219 }
220
221 pub fn edit(
258 &mut self,
259 f: impl FnOnce(&mut LayerEdit<'_>) -> Result<(), AuthoringError>,
260 ) -> Result<bool, EditError> {
261 edit_layers(&mut [self], |edits| f(&mut edits[0]).map_err(EditError::from))
262 }
263
264 fn prepare_commit(&mut self, generation: u64) -> Result<(), sink::Error> {
271 self.changes.update(&self.data);
272 if !self.changes.is_empty() && !self.sinks.is_empty() {
273 let change = PendingLayerChange {
274 layer_identifier: &self.identifier,
275 base: &**self.data.base(),
276 overlay: self.data.overlay(),
277 change_list: &self.changes,
278 generation,
279 };
280 self.sinks.iter().try_for_each(|sink| sink.before_commit(&change))?;
281 }
282 Ok(())
283 }
284
285 fn commit_data(&mut self) -> bool {
291 self.data.commit();
292 !self.changes.is_empty()
293 }
294
295 fn notify_after_commit(&self) {
300 if !self.changes.is_empty() {
301 for sink in self.sinks.iter() {
302 sink.after_commit(&self.identifier, &self.changes);
303 }
304 }
305 }
306
307 pub fn is_dirty(&self) -> bool {
314 !self.data.is_empty()
315 }
316
317 pub(crate) fn discard_changes(&mut self) {
322 self.data.commit();
323 }
324
325 pub fn identifier(&self) -> &str {
327 &self.identifier
328 }
329
330 pub fn resolved_path(&self) -> Option<&str> {
338 (!self.is_anonymous()).then(|| self.real_path())
339 }
340
341 pub(crate) fn real_path(&self) -> &str {
346 self.real_path.as_deref().unwrap_or(&self.identifier)
347 }
348
349 pub(crate) fn anchor_location(&self) -> Option<ar::ResolvedPath> {
353 (!self.is_anonymous()).then(|| ar::ResolvedPath::new(self.real_path()))
354 }
355}
356
357impl Layer {
358 pub fn export(&self, filename: impl AsRef<str>) -> Result<(), ExportError> {
375 let filename = filename.as_ref();
376 let format = super::LayerRegistry::export_format(filename)?;
377 let mut temp = tf::SafeOutputFile::create(FsPath::new(filename)).map_err(|source| ExportError::Create {
378 filename: filename.to_owned(),
379 source,
380 })?;
381 format.write(self.data(), temp.file())?;
382 temp.persist().map_err(|source| ExportError::Replace {
383 filename: filename.to_owned(),
384 source,
385 })
386 }
387
388 pub fn export_to_string(&self) -> Result<String, ExportError> {
394 let format = super::LayerRegistry::find_by_id("usda").expect("usda is a built-in format");
395 let mut buf = Cursor::new(Vec::new());
396 format.write(self.data(), &mut buf)?;
397 Ok(String::from_utf8(buf.into_inner()).expect("the usda writer emits UTF-8 text"))
398 }
399
400 pub fn save(&mut self) -> Result<(), ExportError> {
443 if self.is_anonymous() {
444 return Err(ExportError::Anonymous {
445 identifier: self.identifier.clone(),
446 });
447 }
448 let path = FsPath::new(&self.identifier);
449 if !path.is_absolute() {
450 return Err(ExportError::NotAFilePath {
451 identifier: self.identifier.clone(),
452 });
453 }
454 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or_default();
461 if let Some(format) = super::LayerRegistry::find_by_extension(ext)
462 && !format.caps().can_edit()
463 {
464 return Err(ExportError::NotEditable {
465 identifier: self.identifier.clone(),
466 format: format.format_id(),
467 });
468 }
469 let format = super::LayerRegistry::export_format(&self.identifier)?;
483 let written = format.write_bytes(self.data())?;
484 let create = |source| ExportError::Create {
485 filename: self.identifier.clone(),
486 source,
487 };
488 let mut temp = tf::SafeOutputFile::create(path).map_err(create)?;
489 temp.file().write_all(&written.bytes).map_err(create)?;
490 let replaced = temp.persist().map_err(|source| ExportError::Replace {
491 filename: self.identifier.clone(),
492 source,
493 });
494 if let Some(serialized) = written.data {
495 let reopened = match &replaced {
496 Ok(()) => self.reopen_saved(&written.bytes, format),
497 Err(_) => None,
498 };
499 self.data = CowData::new(reopened.unwrap_or(serialized));
500 }
501 replaced
502 }
503
504 fn reopen_saved(&self, written: &ar::SharedBuffer, format: &dyn super::FileFormat) -> Option<LayerData> {
508 let resolved = ar::ResolvedPath::new(self.real_path());
509 let saved = self.resolver.as_ref()?.open_asset(&resolved).ok()?.shared_buffer()?;
510 if !saved.is_mapping() || saved[..] != written[..] {
511 return None;
512 }
513 format
514 .read_bytes(ar::AssetBuffer::Shared(saved), &resolved.to_string())
515 .ok()
516 }
517}
518
519#[derive(Debug, thiserror::Error)]
522#[non_exhaustive]
523pub enum ExportError {
524 #[error("layer path {filename} has no extension; cannot choose format")]
526 NoExtension {
527 filename: String,
529 },
530
531 #[error("unsupported layer extension {extension:?} (expected usda/usdc/usd/usdz)")]
533 UnsupportedExtension {
534 extension: String,
536 },
537
538 #[error("file format {format} does not support writing")]
540 NotWritable {
541 format: tf::Token,
543 },
544
545 #[error("cannot save anonymous layer {identifier}")]
548 Anonymous {
549 identifier: String,
551 },
552
553 #[error(
556 "cannot save layer {identifier}: identifier is not an absolute file path; \
557 use Layer::export(path) to choose a destination"
558 )]
559 NotAFilePath {
560 identifier: String,
562 },
563
564 #[error(
567 "cannot save layer {identifier} in place: the {format} format is not editable; \
568 use Layer::export(path) to write a new copy"
569 )]
570 NotEditable {
571 identifier: String,
573 format: tf::Token,
575 },
576
577 #[error("failed to create {filename}")]
579 Create {
580 filename: String,
582 #[source]
584 source: io::Error,
585 },
586
587 #[error("failed to replace {filename}")]
590 Replace {
591 filename: String,
593 #[source]
595 source: io::Error,
596 },
597
598 #[error(transparent)]
600 Format(#[from] FormatError),
601}
602
603#[derive(Debug, thiserror::Error)]
605#[non_exhaustive]
606pub enum AuthoringError {
607 #[error(transparent)]
609 Parse(#[from] PathParseError),
610
611 #[error(transparent)]
613 Spec(#[from] SpecError),
614
615 #[error(transparent)]
618 Data(#[from] DataError),
619
620 #[error(transparent)]
623 ValueType(#[from] ValueTypeError),
624
625 #[error("path {path} is not valid here: {reason}")]
629 InvalidPath {
630 path: Path,
632 reason: &'static str,
634 },
635}
636
637#[derive(Debug, thiserror::Error)]
640pub enum EditError {
641 #[error(transparent)]
643 Author(#[from] AuthoringError),
644
645 #[error(transparent)]
648 Rejected(#[from] sink::Error),
649}
650
651pub trait LayerSink {
660 fn before_commit(&self, change: &PendingLayerChange<'_>) -> Result<(), sink::Error> {
665 let _ = change;
666 Ok(())
667 }
668
669 fn after_commit(&self, layer: &str, changes: &ChangeList) {
673 let _ = (layer, changes);
674 }
675}
676
677impl<F: Fn(&str, &ChangeList)> LayerSink for F {
682 fn after_commit(&self, layer: &str, changes: &ChangeList) {
683 self(layer, changes);
684 }
685}
686
687pub struct PendingLayerChange<'a> {
692 pub layer_identifier: &'a str,
694 pub base: &'a dyn AbstractData,
696 pub overlay: &'a HashMap<Path, Patch>,
698 pub change_list: &'a ChangeList,
702 pub generation: u64,
707}
708
709impl Layer {
720 pub fn new_anonymous(tag: impl fmt::Display) -> Self {
730 Self::new_in_memory(Self::anonymous_identifier(tag))
731 }
732
733 pub(crate) fn anonymous_identifier(tag: impl fmt::Display) -> String {
736 let n = crate::next_id();
739 format!("{ANONYMOUS_PREFIX}{n}:{tag}")
740 }
741
742 pub(crate) fn new_in_memory(identifier: impl Into<String>) -> Self {
748 let mut data = Data::new();
749 data.create_spec(Path::abs_root(), SpecType::PseudoRoot);
750 Self::new(identifier, Box::new(data))
751 }
752
753 pub fn is_anonymous(&self) -> bool {
758 Self::is_anonymous_identifier(&self.identifier)
759 }
760
761 pub fn is_anonymous_identifier(identifier: &str) -> bool {
766 identifier.starts_with(ANONYMOUS_PREFIX)
767 }
768
769 pub fn prim(&self, path: impl IntoPath) -> Result<Option<PrimSpecRef<'_>>, PathParseError> {
772 Ok(PrimSpecRef::get(self.data(), try_into_path(path)?))
773 }
774
775 pub fn attribute(&self, path: impl IntoPath) -> Result<Option<AttributeSpecRef<'_>>, PathParseError> {
777 Ok(AttributeSpecRef::get(self.data(), try_into_path(path)?))
778 }
779
780 pub fn relationship(&self, path: impl IntoPath) -> Result<Option<RelationshipSpecRef<'_>>, PathParseError> {
782 Ok(RelationshipSpecRef::get(self.data(), try_into_path(path)?))
783 }
784
785 pub fn pseudo_root(&self) -> Option<PseudoRootSpecRef<'_>> {
788 PseudoRootSpecRef::get(self.data())
789 }
790
791 pub fn relocates(&self) -> RelocateList {
796 self.pseudo_root().and_then(|root| root.relocates()).unwrap_or_default()
797 }
798
799 pub fn has_relocates(&self) -> bool {
803 self.has_root_field(FieldKey::LayerRelocates)
804 }
805
806 pub fn start_time_code(&self) -> f64 {
809 self.pseudo_root()
810 .and_then(|root| root.start_time_code())
811 .unwrap_or(0.0)
812 }
813
814 pub fn has_start_time_code(&self) -> bool {
817 self.has_root_field(FieldKey::StartTimeCode)
818 }
819
820 pub fn end_time_code(&self) -> f64 {
823 self.pseudo_root().and_then(|root| root.end_time_code()).unwrap_or(0.0)
824 }
825
826 pub fn has_end_time_code(&self) -> bool {
829 self.has_root_field(FieldKey::EndTimeCode)
830 }
831
832 pub fn time_codes_per_second(&self) -> f64 {
836 let root = self.pseudo_root();
837 root.as_ref()
838 .and_then(|root| root.time_codes_per_second())
839 .or_else(|| root.as_ref().and_then(|root| root.frames_per_second()))
840 .unwrap_or(24.0)
841 }
842
843 pub fn has_time_codes_per_second(&self) -> bool {
846 self.has_root_field(FieldKey::TimeCodesPerSecond)
847 }
848
849 pub fn frames_per_second(&self) -> f64 {
852 self.pseudo_root()
853 .and_then(|root| root.frames_per_second())
854 .unwrap_or(24.0)
855 }
856
857 pub fn has_frames_per_second(&self) -> bool {
860 self.has_root_field(FieldKey::FramesPerSecond)
861 }
862
863 pub fn frame_precision(&self) -> i32 {
866 self.pseudo_root().and_then(|root| root.frame_precision()).unwrap_or(3)
867 }
868
869 pub fn has_frame_precision(&self) -> bool {
872 self.has_root_field(FieldKey::FramePrecision)
873 }
874
875 fn has_root_field(&self, key: FieldKey) -> bool {
878 self.data.has_field(&Path::abs_root(), key.as_str())
879 }
880}
881
882pub struct LayerEdit<'a> {
891 layer: &'a mut Layer,
892}
893
894impl std::ops::Deref for LayerEdit<'_> {
895 type Target = Layer;
896
897 fn deref(&self) -> &Layer {
898 &*self.layer
899 }
900}
901
902impl LayerEdit<'_> {
903 pub fn data_mut(&mut self) -> &mut dyn AbstractData {
908 &mut self.layer.data
909 }
910
911 pub fn remove_spec(&mut self, path: &Path) -> Result<bool, AuthoringError> {
915 super::spec::remove_spec(self.data_mut(), path)
916 }
917
918 pub fn prim_mut(&mut self, path: impl IntoPath) -> Result<Option<PrimSpecMut<'_>>, PathParseError> {
920 Ok(PrimSpecMut::get(self.data_mut(), try_into_path(path)?))
921 }
922
923 pub fn attribute_mut(&mut self, path: impl IntoPath) -> Result<Option<AttributeSpecMut<'_>>, PathParseError> {
925 Ok(AttributeSpecMut::get(self.data_mut(), try_into_path(path)?))
926 }
927
928 pub fn relationship_mut(&mut self, path: impl IntoPath) -> Result<Option<RelationshipSpecMut<'_>>, PathParseError> {
930 Ok(RelationshipSpecMut::get(self.data_mut(), try_into_path(path)?))
931 }
932
933 pub fn pseudo_root_mut(&mut self) -> Result<PseudoRootSpecMut<'_>, AuthoringError> {
937 let root = Path::abs_root();
938 match self.layer.data().spec_type(&root) {
939 Some(SpecType::PseudoRoot) => {}
940 Some(_) => {
941 return Err(AuthoringError::InvalidPath {
942 path: root,
943 reason: "root spec exists with non-PseudoRoot SpecType",
944 });
945 }
946 None => self.data_mut().create_spec(root, SpecType::PseudoRoot),
947 }
948 Ok(PseudoRootSpecMut::get(self.data_mut()).expect("just ensured a pseudo-root spec"))
949 }
950
951 pub fn set_default_prim(&mut self, name: impl Into<String>) -> Result<(), AuthoringError> {
963 let name = name.into();
964 if default_prim_path(&name).is_none() {
965 return Err(AuthoringError::InvalidPath {
966 path: Path::abs_root(),
967 reason: "defaultPrim must name a prim",
968 });
969 }
970 self.pseudo_root_mut()?.set_default_prim(name);
971 Ok(())
972 }
973
974 pub fn clear_default_prim(&mut self) -> Result<(), AuthoringError> {
977 self.clear_root_field(FieldKey::DefaultPrim)
978 }
979
980 pub fn set_relocates(&mut self, relocates: RelocateList) -> Result<(), AuthoringError> {
984 self.pseudo_root_mut()?.set_relocates(relocates);
985 Ok(())
986 }
987
988 pub fn clear_relocates(&mut self) -> Result<(), AuthoringError> {
990 self.clear_root_field(FieldKey::LayerRelocates)
991 }
992
993 pub fn set_expression_variables(&mut self, vars: HashMap<String, Value>) -> Result<(), AuthoringError> {
997 self.pseudo_root_mut()?.set_expression_variables(vars);
998 Ok(())
999 }
1000
1001 pub fn clear_expression_variables(&mut self) -> Result<(), AuthoringError> {
1003 self.clear_root_field(FieldKey::ExpressionVariables)
1004 }
1005
1006 pub fn set_start_time_code(&mut self, time: f64) -> Result<(), AuthoringError> {
1008 self.pseudo_root_mut()?.set_start_time_code(time);
1009 Ok(())
1010 }
1011
1012 pub fn clear_start_time_code(&mut self) -> Result<(), AuthoringError> {
1014 self.clear_root_field(FieldKey::StartTimeCode)
1015 }
1016
1017 pub fn set_end_time_code(&mut self, time: f64) -> Result<(), AuthoringError> {
1019 self.pseudo_root_mut()?.set_end_time_code(time);
1020 Ok(())
1021 }
1022
1023 pub fn clear_end_time_code(&mut self) -> Result<(), AuthoringError> {
1025 self.clear_root_field(FieldKey::EndTimeCode)
1026 }
1027
1028 pub fn set_time_codes_per_second(&mut self, rate: f64) -> Result<(), AuthoringError> {
1030 self.pseudo_root_mut()?.set_time_codes_per_second(rate);
1031 Ok(())
1032 }
1033
1034 pub fn clear_time_codes_per_second(&mut self) -> Result<(), AuthoringError> {
1036 self.clear_root_field(FieldKey::TimeCodesPerSecond)
1037 }
1038
1039 pub fn set_frames_per_second(&mut self, rate: f64) -> Result<(), AuthoringError> {
1041 self.pseudo_root_mut()?.set_frames_per_second(rate);
1042 Ok(())
1043 }
1044
1045 pub fn clear_frames_per_second(&mut self) -> Result<(), AuthoringError> {
1047 self.clear_root_field(FieldKey::FramesPerSecond)
1048 }
1049
1050 pub fn set_frame_precision(&mut self, precision: i32) -> Result<(), AuthoringError> {
1052 self.pseudo_root_mut()?.set_frame_precision(precision);
1053 Ok(())
1054 }
1055
1056 pub fn clear_frame_precision(&mut self) -> Result<(), AuthoringError> {
1058 self.clear_root_field(FieldKey::FramePrecision)
1059 }
1060
1061 fn clear_root_field(&mut self, key: FieldKey) -> Result<(), AuthoringError> {
1064 self.data_mut().erase_field(&Path::abs_root(), key.as_str());
1066 Ok(())
1067 }
1068}
1069
1070impl std::fmt::Debug for Layer {
1071 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1072 f.debug_struct("Layer")
1073 .field("identifier", &self.identifier)
1074 .finish_non_exhaustive()
1075 }
1076}
1077
1078pub fn default_prim_path(token: &str) -> Option<Path> {
1101 let path = Path::new(token).ok()?;
1102 if !path.is_prim_path() || path.as_str().starts_with('.') {
1105 return None;
1106 }
1107 Some(Path::abs_root().make_absolute(&path))
1108}
1109
1110pub(crate) fn edit_layers<E: From<sink::Error>>(
1122 layers: &mut [&mut Layer],
1123 f: impl FnOnce(&mut [LayerEdit<'_>]) -> Result<(), E>,
1124) -> Result<bool, E> {
1125 let mut guard = GroupEditGuard {
1126 layers,
1127 committed: false,
1128 };
1129 {
1130 let mut edits: Vec<LayerEdit<'_>> = guard.layers.iter_mut().map(|layer| LayerEdit { layer }).collect();
1131 f(&mut edits)?;
1132 }
1135 let generation = crate::next_id();
1138 for layer in guard.layers.iter_mut() {
1141 layer.prepare_commit(generation)?;
1142 }
1143 let mut changed = false;
1147 for layer in guard.layers.iter_mut() {
1148 changed |= layer.commit_data();
1149 }
1150 guard.committed = true;
1152 for layer in guard.layers.iter() {
1155 layer.notify_after_commit();
1156 }
1157 Ok(changed)
1158}
1159
1160pub(crate) fn dry_run_layers<E>(
1166 layers: &mut [&mut Layer],
1167 f: impl FnOnce(&mut [LayerEdit<'_>]) -> Result<(), E>,
1168) -> Result<(), E> {
1169 let guard = GroupEditGuard {
1172 layers,
1173 committed: false,
1174 };
1175 let mut edits: Vec<LayerEdit<'_>> = guard.layers.iter_mut().map(|layer| LayerEdit { layer }).collect();
1176 f(&mut edits)
1177}
1178
1179struct GroupEditGuard<'a, 'b> {
1183 layers: &'b mut [&'a mut Layer],
1184 committed: bool,
1185}
1186
1187impl Drop for GroupEditGuard<'_, '_> {
1188 fn drop(&mut self) {
1189 if self.committed {
1190 return;
1191 }
1192 for layer in self.layers.iter_mut() {
1193 layer.rollback();
1194 }
1195 }
1196}
1197
1198#[cfg(test)]
1199mod tests {
1200 use super::*;
1201 use crate::Result;
1202
1203 use std::cell::{Cell, RefCell};
1204 use std::fs;
1205 use std::io;
1206 use std::path::PathBuf;
1207 use std::rc::Rc;
1208 use std::sync::Arc;
1209 use std::sync::atomic::{AtomicUsize, Ordering};
1210
1211 use crate::sdf;
1212 use crate::usda;
1213
1214 #[test]
1218 fn open_canonicalizes_identifier() -> Result<()> {
1219 let dir = tempfile::tempdir()?;
1220 let path = dir.path().join("scene.usda");
1221 std::fs::write(&path, "#usda 1.0\n")?;
1222
1223 let indirect = dir.path().join(".").join("scene.usda");
1225 let mut layer = Layer::open(indirect.to_string_lossy())?;
1226
1227 assert_eq!(
1228 layer.identifier(),
1229 path.canonicalize()?.to_string_lossy(),
1230 "the identifier is the canonical one, not the spelling passed in"
1231 );
1232 layer
1233 .save()
1234 .expect("a layer that loaded from a file can save back to it");
1235 Ok(())
1236 }
1237
1238 fn edit_layer(layer: &mut Layer, f: impl FnOnce(&mut LayerEdit<'_>)) {
1241 layer
1242 .edit(|e| {
1243 f(e);
1244 Ok(())
1245 })
1246 .expect("authored");
1247 }
1248
1249 fn crate_fixture(dir: &FsPath) -> Result<PathBuf> {
1252 let path = dir.join("scene.usdc");
1253 let text = format!(
1254 "#usda 1.0\ndef \"World\"\n{{\n float[] size = [{}]\n}}\n",
1255 ["1"; 64].join(", ")
1256 );
1257 Layer::from_bytes("scene", text.into_bytes())?.export(path.to_string_lossy())?;
1258 Ok(path)
1259 }
1260
1261 fn world_kind(layer: &Layer) -> Option<tf::Token> {
1263 layer.prim("/World").unwrap().expect("the prim exists").kind()
1264 }
1265
1266 fn set_component(layer: &mut Layer) {
1269 edit_layer(layer, |e| {
1270 e.prim_mut("/World")
1271 .unwrap()
1272 .expect("the prim exists")
1273 .set_kind("component");
1274 });
1275 }
1276
1277 #[test]
1280 fn save_crate_over_itself() -> Result<()> {
1281 let dir = tempfile::tempdir()?;
1282 let path = crate_fixture(dir.path())?;
1283 let mut layer = Layer::open(path.to_string_lossy())?;
1284 set_component(&mut layer);
1285 layer.save()?;
1286
1287 let component = Some(tf::Token::from("component"));
1288 assert_eq!(world_kind(&layer), component);
1289 let size = layer
1290 .data()
1291 .get_field(&Path::new("/World.size")?, "default")
1292 .expect("decoded on demand")
1293 .into_owned();
1294 assert_eq!(size, Value::FloatVec(vec![1.0; 64]));
1295 assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
1296 assert_eq!(fs::read_dir(dir.path())?.count(), 1, "no temporary file remains");
1297 Ok(())
1298 }
1299
1300 #[test]
1303 fn replace_failure_keeps_edits() -> Result<()> {
1304 let dir = tempfile::tempdir()?;
1305 let path = crate_fixture(dir.path())?;
1306 let mut layer = Layer::open(path.to_string_lossy())?;
1307 set_component(&mut layer);
1308 fs::remove_file(&path)?;
1311 fs::create_dir(&path)?;
1312 let error = layer.save().expect_err("the replacement fails");
1313 assert!(matches!(error, ExportError::Replace { .. }), "{error:?}");
1314 let component = Some(tf::Token::from("component"));
1315 assert_eq!(world_kind(&layer), component, "the edit survives in memory");
1316 assert_eq!(fs::read_dir(dir.path())?.count(), 1, "no temporary file remains");
1317
1318 fs::remove_dir(&path)?;
1319 layer.save()?;
1320 assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
1321 Ok(())
1322 }
1323
1324 #[cfg(unix)]
1327 #[test]
1328 fn export_through_symlink() -> Result<()> {
1329 use std::os::unix::fs::symlink;
1330 let dir = tempfile::tempdir()?;
1331 let target = crate_fixture(dir.path())?;
1332 let link = dir.path().join("link.usdc");
1333 symlink(&target, &link)?;
1334 let mut layer = Layer::open(target.to_string_lossy())?;
1335 set_component(&mut layer);
1336 layer.export(link.to_string_lossy())?;
1337 assert!(
1338 fs::symlink_metadata(&link)?.file_type().is_symlink(),
1339 "the link is kept"
1340 );
1341 let component = Some(tf::Token::from("component"));
1342 assert_eq!(world_kind(&Layer::open(target.to_string_lossy())?), component);
1343 Ok(())
1344 }
1345
1346 #[cfg(windows)]
1350 #[test]
1351 fn save_keeps_acl() -> Result<()> {
1352 use std::env;
1353 use std::process::Command;
1354 let dir = tempfile::tempdir()?;
1355 let path = crate_fixture(dir.path())?;
1356 let file = path.to_str().expect("utf-8 temp path");
1357 let icacls = |args: &[&str]| -> Result<String> {
1358 let output = Command::new("icacls").args(args).output()?;
1359 assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
1360 Ok(String::from_utf8_lossy(&output.stdout).into_owned())
1361 };
1362 let grant = format!("{}:(R,W)", env::var("USERNAME").expect("USERNAME is set"));
1363 icacls(&[file, "/inheritance:r", "/grant:r", &grant])?;
1364 let before = icacls(&[file])?;
1365 assert!(before.contains("(R,W)"), "{before}");
1366
1367 let mut layer = Layer::open(file)?;
1368 set_component(&mut layer);
1369 layer.save()?;
1370 assert_eq!(icacls(&[file])?, before, "the list survives the save");
1371 layer.export(file)?;
1372 assert_eq!(icacls(&[file])?, before, "the list survives the export");
1373 assert_eq!(world_kind(&Layer::open(file)?), Some(tf::Token::from("component")));
1374 Ok(())
1375 }
1376
1377 #[cfg(unix)]
1380 #[test]
1381 fn save_keeps_permissions() -> Result<()> {
1382 use std::os::unix::fs::PermissionsExt;
1383 let dir = tempfile::tempdir()?;
1384 let path = crate_fixture(dir.path())?;
1385 fs::set_permissions(&path, fs::Permissions::from_mode(0o600))?;
1386 let mut layer = Layer::open(path.to_string_lossy())?;
1387 layer.save()?;
1388 assert_eq!(fs::metadata(&path)?.permissions().mode() & 0o777, 0o600);
1389 layer.export(path.to_string_lossy())?;
1390 assert_eq!(fs::metadata(&path)?.permissions().mode() & 0o777, 0o600);
1391 Ok(())
1392 }
1393
1394 #[test]
1397 fn export_leaves_no_temp() -> Result<()> {
1398 let dir = tempfile::tempdir()?;
1399 let path = crate_fixture(dir.path())?;
1400 let layer = Layer::open(path.to_string_lossy())?;
1401 layer.export(path.to_string_lossy())?;
1402 assert_eq!(fs::read_dir(dir.path())?.count(), 1);
1403 let missing = dir.path().join("missing").join("scene.usdc");
1404 let error = layer.export(missing.to_string_lossy()).expect_err("no such directory");
1405 assert!(matches!(error, ExportError::Create { .. }), "{error:?}");
1406 assert_eq!(fs::read_dir(dir.path())?.count(), 1);
1407 Ok(())
1408 }
1409
1410 #[cfg(feature = "mmap")]
1414 #[test]
1415 #[allow(unsafe_code)]
1418 fn save_with_other_view() -> Result<()> {
1419 let dir = tempfile::tempdir()?;
1420 let path = crate_fixture(dir.path())?;
1421 let resolver = unsafe { ar::DefaultResolver::new().map_files() };
1425 let registry = crate::sdf::LayerRegistry::new(Box::new(resolver));
1426 let identifier = registry.create_identifier(&path.to_string_lossy(), None);
1427 let mut editing = registry.open_layer(&identifier)?.expect("the layer resolves");
1428 let other = registry.open_layer(&identifier)?.expect("the layer resolves");
1429 set_component(&mut editing);
1430
1431 editing.save()?;
1432 let component = Some(tf::Token::from("component"));
1433 assert_eq!(world_kind(&editing), component);
1434 assert_eq!(world_kind(&other), None, "the other view reads the file it mapped");
1435 let size = other
1436 .data()
1437 .get_field(&Path::new("/World.size")?, "default")
1438 .expect("still decodes from its view")
1439 .into_owned();
1440 assert_eq!(size, Value::FloatVec(vec![1.0; 64]));
1441 assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
1442 Ok(())
1443 }
1444
1445 #[test]
1449 fn anonymous_identifiers_unique() {
1450 let a = Layer::new_anonymous("scene.usda");
1451 let b = Layer::new_anonymous("scene.usda");
1452 assert_ne!(a.identifier(), b.identifier());
1453 assert!(a.identifier().ends_with(":scene.usda"));
1454 assert!(a.is_anonymous() && b.is_anonymous());
1455 assert!(!Layer::new_in_memory("scene.usda").is_anonymous());
1456 }
1457
1458 #[test]
1461 fn relocates_round_trip() {
1462 let mut layer = Layer::new_anonymous("test.usda");
1463 assert!(!layer.has_relocates());
1464 assert!(layer.relocates().is_empty());
1465
1466 let pairs = vec![
1467 (Path::new("/Rig/Model").unwrap(), Path::new("/Group/Model").unwrap()),
1468 (Path::new("/Rig/Dead").unwrap(), Path::default()),
1469 ];
1470 edit_layer(&mut layer, |e| {
1471 e.set_relocates(pairs.clone()).unwrap();
1472 });
1473
1474 assert!(layer.has_relocates());
1475 assert_eq!(layer.relocates(), pairs);
1476 }
1477
1478 #[test]
1481 fn empty_opinion_vs_cleared() {
1482 let mut layer = Layer::new_anonymous("test.usda");
1483
1484 edit_layer(&mut layer, |e| {
1485 e.set_relocates(RelocateList::new()).unwrap();
1486 });
1487 assert!(layer.has_relocates());
1488 assert!(layer.relocates().is_empty());
1489
1490 edit_layer(&mut layer, |e| {
1491 e.clear_relocates().unwrap();
1492 });
1493 assert!(!layer.has_relocates());
1494 assert!(layer.relocates().is_empty());
1495 }
1496
1497 #[test]
1500 fn time_code_round_trip() {
1501 let mut layer = Layer::new_anonymous("test.usda");
1502 assert!(!layer.has_start_time_code());
1503 assert_eq!(layer.start_time_code(), 0.0);
1504 assert_eq!(layer.end_time_code(), 0.0);
1505 assert_eq!(layer.frame_precision(), 3);
1506
1507 edit_layer(&mut layer, |e| {
1508 e.set_start_time_code(1.0).unwrap();
1509 e.set_end_time_code(48.0).unwrap();
1510 e.set_frame_precision(6).unwrap();
1511 });
1512
1513 assert!(layer.has_start_time_code());
1514 assert_eq!(layer.start_time_code(), 1.0);
1515 assert_eq!(layer.end_time_code(), 48.0);
1516 assert_eq!(layer.frame_precision(), 6);
1517 }
1518
1519 #[test]
1522 fn tcps_fps_fallback() {
1523 let mut layer = Layer::new_anonymous("test.usda");
1524 assert_eq!(layer.time_codes_per_second(), 24.0);
1525 assert_eq!(layer.frames_per_second(), 24.0);
1526
1527 edit_layer(&mut layer, |e| {
1528 e.set_frames_per_second(30.0).unwrap();
1529 });
1530 assert_eq!(layer.time_codes_per_second(), 30.0);
1531
1532 edit_layer(&mut layer, |e| {
1533 e.set_time_codes_per_second(48.0).unwrap();
1534 });
1535 assert_eq!(layer.time_codes_per_second(), 48.0);
1536 }
1537
1538 #[test]
1541 fn clear_time_code() {
1542 let mut layer = Layer::new_anonymous("test.usda");
1543 edit_layer(&mut layer, |e| {
1544 e.set_start_time_code(5.0).unwrap();
1545 });
1546 assert!(layer.has_start_time_code());
1547
1548 edit_layer(&mut layer, |e| {
1549 e.clear_start_time_code().unwrap();
1550 });
1551 assert!(!layer.has_start_time_code());
1552 assert_eq!(layer.start_time_code(), 0.0);
1553 }
1554
1555 #[test]
1557 fn edit_commits() {
1558 use crate::sdf::{PrimSpec, Specifier};
1559 let mut layer = Layer::new_anonymous("test.usda");
1560 let changed = layer
1561 .edit(|l| {
1562 PrimSpec::new(l.data_mut(), "/World", Specifier::Def, "Xform")?;
1563 Ok(())
1564 })
1565 .expect("no error or veto");
1566 assert!(changed);
1567 assert!(layer.prim(Path::new("/World").unwrap()).unwrap().is_some());
1568 }
1569
1570 #[test]
1573 fn edit_rolls_back_on_error() {
1574 use crate::sdf::{PrimSpec, Specifier};
1575 let mut layer = Layer::new_anonymous("test.usda");
1576 let result: Result<bool, EditError> = layer.edit(|l| {
1577 PrimSpec::new(l.data_mut(), "/A", Specifier::Def, "Xform")?;
1578 Err(AuthoringError::InvalidPath {
1579 path: Path::abs_root(),
1580 reason: "test",
1581 })
1582 });
1583 assert!(result.is_err());
1584 assert!(
1585 layer.prim(Path::new("/A").unwrap()).unwrap().is_none(),
1586 "the staged edit rolled back"
1587 );
1588 assert!(!layer.is_dirty());
1589 }
1590
1591 #[test]
1594 fn edit_recovers_on_panic() {
1595 use crate::sdf::{PrimSpec, Specifier};
1596 use std::panic::{AssertUnwindSafe, catch_unwind};
1597
1598 let mut layer = Layer::new_anonymous("test.usda");
1599 let panicked = catch_unwind(AssertUnwindSafe(|| {
1600 let _: Result<bool, EditError> = layer.edit(|l| {
1601 PrimSpec::new(l.data_mut(), "/A", Specifier::Def, "Xform").expect("authored");
1602 panic!("boom");
1603 });
1604 }));
1605 assert!(panicked.is_err(), "the panic propagates");
1606 assert!(
1607 layer.prim(Path::new("/A").unwrap()).unwrap().is_none(),
1608 "the staged edit rolled back"
1609 );
1610 assert!(!layer.is_dirty(), "no staged edits or recorded changes remain");
1611
1612 let changed = layer
1614 .edit(|l| {
1615 PrimSpec::new(l.data_mut(), "/B", Specifier::Def, "Xform")?;
1616 Ok(())
1617 })
1618 .expect("no error");
1619 assert!(changed);
1620 assert!(layer.prim(Path::new("/B").unwrap()).unwrap().is_some());
1621 }
1622
1623 #[test]
1627 fn group_commit_atomic_on_panic() {
1628 use crate::sdf::{PrimSpec, Specifier};
1629 use std::panic::{AssertUnwindSafe, catch_unwind};
1630
1631 let mut a = Layer::new_anonymous("a.usda");
1632 let mut b = Layer::new_anonymous("b.usda");
1633 a.add_sink(|_: &str, _: &ChangeList| panic!("boom"));
1634
1635 let panicked = catch_unwind(AssertUnwindSafe(|| {
1636 let _ = edit_layers(&mut [&mut a, &mut b], |edits| {
1637 PrimSpec::new(edits[0].data_mut(), "/A", Specifier::Def, "Xform").expect("authored a");
1638 PrimSpec::new(edits[1].data_mut(), "/B", Specifier::Def, "Xform").expect("authored b");
1639 Ok::<(), EditError>(())
1640 });
1641 }));
1642 assert!(panicked.is_err(), "the after_commit panic propagates");
1643 assert!(
1644 a.prim(Path::new("/A").unwrap()).unwrap().is_some(),
1645 "layer a committed despite the panic"
1646 );
1647 assert!(
1648 b.prim(Path::new("/B").unwrap()).unwrap().is_some(),
1649 "layer b committed despite the panic"
1650 );
1651 }
1652
1653 #[test]
1657 fn discard_changes_keeps_content() {
1658 use crate::sdf::{PrimSpec, Specifier};
1659
1660 let mut layer = Layer::new_anonymous("test.usda");
1661 edit_layer(&mut layer, |e| {
1662 PrimSpec::new(e.data_mut(), "/World", Specifier::Def, "Xform").unwrap();
1663 });
1664
1665 layer.discard_changes();
1666 assert!(!layer.is_dirty());
1667 assert!(
1668 layer.prim(Path::new("/World").unwrap()).unwrap().is_some(),
1669 "the authored content survives"
1670 );
1671 }
1672
1673 #[test]
1677 fn default_prim_path_forms() {
1678 for (token, want) in [
1679 ("/foo", "/foo"),
1680 ("/foo/bar", "/foo/bar"),
1681 ("foo", "/foo"),
1682 ("foo/bar", "/foo/bar"),
1683 ] {
1684 assert_eq!(default_prim_path(token).as_ref().map(Path::as_str), Some(want));
1685 }
1686 for token in ["/foo.prop", "/", "//", "", "..", "."] {
1687 assert_eq!(default_prim_path(token), None, "{token} names no prim");
1688 }
1689 }
1690
1691 #[test]
1694 fn set_default_prim_forms() {
1695 let mut layer = Layer::new_anonymous("anon.usda");
1696 for name in ["World", "World/Char", "/World/Char"] {
1697 edit_layer(&mut layer, |e| {
1698 e.set_default_prim(name).expect("names a prim");
1699 });
1700 assert_eq!(layer.pseudo_root().unwrap().default_prim().as_deref(), Some(name));
1701 }
1702 edit_layer(&mut layer, |e| {
1703 assert!(e.set_default_prim("World.attr").is_err(), "a property names no prim");
1704 assert!(e.set_default_prim("/").is_err(), "the pseudo-root names no prim");
1705 });
1706 }
1707
1708 #[test]
1712 fn metadata_on_rootless_backend() {
1713 let root = Path::abs_root();
1714 let default_prim = FieldKey::DefaultPrim.as_str();
1715
1716 let mut layer = Layer::new("rootless.usda", Box::new(Data::new()));
1717 edit_layer(&mut layer, |e| {
1718 e.set_default_prim("World").unwrap();
1719 });
1720 assert!(layer.data().has_field(&root, default_prim));
1721
1722 edit_layer(&mut layer, |e| {
1723 e.clear_default_prim().unwrap();
1724 });
1725 assert!(!layer.data().has_field(&root, default_prim));
1726 }
1727
1728 #[derive(Default)]
1730 struct Recorder {
1731 before: std::cell::Cell<u32>,
1732 after: std::cell::Cell<u32>,
1733 veto: bool,
1734 }
1735
1736 impl LayerSink for std::rc::Rc<Recorder> {
1737 fn before_commit(&self, _change: &PendingLayerChange<'_>) -> Result<(), sink::Error> {
1738 self.before.set(self.before.get() + 1);
1739 if self.veto {
1740 return Err(sink::Error::new("vetoed"));
1741 }
1742 Ok(())
1743 }
1744 fn after_commit(&self, _layer: &str, _changes: &ChangeList) {
1745 self.after.set(self.after.get() + 1);
1746 }
1747 }
1748
1749 #[test]
1752 fn layer_sink_fires() {
1753 use crate::sdf::{PrimSpec, Specifier};
1754 let mut layer = Layer::new_anonymous("test.usda");
1755 let rec = std::rc::Rc::new(Recorder::default());
1756 layer.add_sink(rec.clone());
1757
1758 layer
1759 .edit(|l| {
1760 PrimSpec::new(l.data_mut(), "/World", Specifier::Def, "Xform")?;
1761 Ok(())
1762 })
1763 .expect("sink accepts");
1764
1765 assert_eq!((rec.before.get(), rec.after.get()), (1, 1));
1766 assert!(layer.prim(Path::new("/World").unwrap()).unwrap().is_some());
1767 }
1768
1769 #[test]
1772 fn layer_sink_veto_rolls_back() {
1773 use crate::sdf::{PrimSpec, Specifier};
1774 let mut layer = Layer::new_anonymous("test.usda");
1775 let rec = std::rc::Rc::new(Recorder {
1776 veto: true,
1777 ..Default::default()
1778 });
1779 layer.add_sink(rec.clone());
1780
1781 let result = layer.edit(|l| {
1782 PrimSpec::new(l.data_mut(), "/World", Specifier::Def, "Xform")?;
1783 Ok(())
1784 });
1785 assert!(matches!(result, Err(EditError::Rejected(_))), "the sink vetoes");
1786
1787 assert_eq!((rec.before.get(), rec.after.get()), (1, 0), "after_commit never fired");
1788 assert!(
1789 layer.prim(Path::new("/World").unwrap()).unwrap().is_none(),
1790 "the edit rolled back"
1791 );
1792 assert!(!layer.is_dirty());
1793 }
1794
1795 #[test]
1797 fn layer_sink_removed() {
1798 use crate::sdf::{PrimSpec, Specifier};
1799 let mut layer = Layer::new_anonymous("test.usda");
1800 let rec = std::rc::Rc::new(Recorder::default());
1801 let id = layer.add_sink(rec.clone());
1802 layer.remove_sink(id);
1803
1804 layer
1805 .edit(|l| {
1806 PrimSpec::new(l.data_mut(), "/World", Specifier::Def, "Xform")?;
1807 Ok(())
1808 })
1809 .expect("no sink");
1810 assert_eq!((rec.before.get(), rec.after.get()), (0, 0));
1811 }
1812
1813 #[test]
1814 fn export_dispatches_on_extension() -> Result<()> {
1815 use crate::sdf::{self, SpecType};
1816
1817 let mut data = sdf::Data::new();
1818 let root = sdf::Path::abs_root();
1819 let ps = data.create_spec(root, SpecType::PseudoRoot);
1820 ps.add("primChildren", sdf::Value::TokenVec(vec!["Foo".into()]));
1821 let foo = sdf::path("/Foo")?;
1822 let sp = data.create_spec(foo, SpecType::Prim);
1823 sp.add("specifier", sdf::Value::Specifier(sdf::Specifier::Def));
1824 sp.add("typeName", sdf::Value::Token("Xform".into()));
1825
1826 let layer = sdf::Layer::new("test://layer", Box::new(data));
1827 let dir = tempfile::tempdir()?;
1828
1829 let usda_path = dir.path().join("layer-save.usda");
1830 let usdc_path = dir.path().join("layer-save.usdc");
1831 let usdz_path = dir.path().join("layer-save.usdz");
1832
1833 layer.export(usda_path.to_str().unwrap())?;
1834 layer.export(usdc_path.to_str().unwrap())?;
1835 layer.export(usdz_path.to_str().unwrap())?;
1836
1837 assert!(std::fs::metadata(&usda_path)?.len() > 0);
1838 assert!(std::fs::metadata(&usdc_path)?.len() > 0);
1839 assert!(std::fs::metadata(&usdz_path)?.len() > 0);
1840
1841 let archive = crate::usdz::Archive::open(&usdz_path)?;
1843 let name = archive.first_layer_name().expect("usdz has a layer");
1844 assert!(name.ends_with(".usdc"));
1845 Ok(())
1846 }
1847
1848 #[test]
1849 fn export_usd_writes_binary() -> Result<()> {
1850 use crate::sdf::{self, SpecType};
1851
1852 let mut data = sdf::Data::new();
1853 let root = sdf::Path::abs_root();
1854 let ps = data.create_spec(root, SpecType::PseudoRoot);
1855 ps.add("primChildren", sdf::Value::TokenVec(vec!["Bar".into()]));
1856 let bar = sdf::path("/Bar")?;
1857 let sp = data.create_spec(bar.clone(), SpecType::Prim);
1858 sp.add("specifier", sdf::Value::Specifier(sdf::Specifier::Def));
1859 sp.add("typeName", sdf::Value::Token("Cube".into()));
1860
1861 let layer = sdf::Layer::new("test://layer-usd", Box::new(data));
1862 let dir = tempfile::tempdir()?;
1863 let path = dir.path().join("layer-save.usd");
1864 layer.export(path.to_str().unwrap())?;
1865
1866 let bytes = std::fs::read(&path)?;
1868 assert!(
1869 bytes.starts_with(crate::usdc::MAGIC),
1870 "writer should emit binary for .usd, got magic {:?}",
1871 &bytes[..crate::usdc::MAGIC.len().min(bytes.len())],
1872 );
1873
1874 let round = sdf::LayerRegistry::default()
1876 .open(path.to_str().unwrap())?
1877 .expect("the layer opens")
1878 .data;
1879 assert_eq!(round.spec_type(&bar), Some(SpecType::Prim));
1880 assert_eq!(
1881 round.get_field(&bar, "typeName").unwrap().into_owned(),
1882 sdf::Value::Token("Cube".into())
1883 );
1884 Ok(())
1885 }
1886
1887 #[test]
1888 fn export_rejects_unknown_ext() {
1889 use crate::sdf::{self, SpecType};
1890 let mut data = sdf::Data::new();
1891 data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1892 let layer = sdf::Layer::new("test://layer", Box::new(data));
1893 let err = layer.export("/tmp/openusd-bad.xyz").unwrap_err();
1894 assert!(err.to_string().contains("unsupported"));
1895 }
1896
1897 #[test]
1898 fn save_writes_identifier() -> Result<()> {
1899 use crate::sdf::{self, SpecType};
1900
1901 let mut data = sdf::Data::new();
1902 data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1903
1904 let dir = tempfile::tempdir()?;
1905 let path = dir.path().join("by-identifier.usda");
1906 let mut layer = sdf::Layer::new(path.to_str().unwrap(), Box::new(data));
1907 layer.save()?;
1908
1909 let bytes = std::fs::read(&path)?;
1910 assert!(
1911 bytes.starts_with(b"#usda"),
1912 "save() should write text to a .usda identifier"
1913 );
1914 Ok(())
1915 }
1916
1917 #[test]
1918 fn save_rejects_anonymous() {
1919 let mut layer = sdf::Layer::new_anonymous("scratch");
1920 let err = layer.save().unwrap_err();
1921 assert!(err.to_string().contains("anonymous"));
1922 }
1923
1924 #[test]
1925 fn save_rejects_non_file_identifier() {
1926 use crate::sdf::{self, SpecType};
1927 let mut data = sdf::Data::new();
1928 data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1929 let mut layer = sdf::Layer::new("scheme://host/x.usda", Box::new(data));
1932 let err = layer.save().unwrap_err();
1933 assert!(err.to_string().contains("absolute file path"));
1934 }
1935
1936 #[test]
1937 fn save_rejects_usdz_in_place() {
1938 use crate::sdf::{self, SpecType};
1939 let mut data = sdf::Data::new();
1940 data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1941 let dir = tempfile::tempdir().unwrap();
1944 let path = dir.path().join("pkg.usdz");
1945 let mut layer = sdf::Layer::new(path.to_str().unwrap(), Box::new(data));
1946 let err = layer.save().unwrap_err();
1947 assert!(err.to_string().contains("not editable"));
1948 }
1949
1950 #[test]
1951 fn export_to_string_is_usda() -> Result<()> {
1952 use crate::sdf::{self, SpecType};
1953
1954 let mut data = sdf::Data::new();
1955 let ps = data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1956 ps.add("primChildren", sdf::Value::TokenVec(vec!["Foo".into()]));
1957 let sp = data.create_spec(sdf::path("/Foo")?, SpecType::Prim);
1958 sp.add("specifier", sdf::Value::Specifier(sdf::Specifier::Def));
1959
1960 let layer = sdf::Layer::new("scratch.usdc", Box::new(data));
1962 let text = layer.export_to_string()?;
1963 assert!(text.starts_with("#usda"), "export_to_string must emit usda text");
1964 assert!(text.contains("\"Foo\""), "export_to_string must include the prim");
1965 Ok(())
1966 }
1967
1968 #[test]
1973 fn create_prim_basic() -> Result<()> {
1974 let mut layer = sdf::Layer::new_anonymous("anon.usda");
1975 edit_layer(&mut layer, |e| {
1976 sdf::PrimSpec::new(e.data_mut(), "/World", sdf::Specifier::Def, "Xform")
1977 .unwrap()
1978 .set_kind("group");
1979 });
1980
1981 let world = layer.prim("/World")?.expect("prim authored");
1982 assert_eq!(world.type_name().as_deref(), Some("Xform"));
1983 assert_eq!(world.specifier(), Some(sdf::Specifier::Def));
1984 assert_eq!(world.kind().as_deref(), Some("group"));
1985 Ok(())
1986 }
1987
1988 #[test]
1989 fn auto_ancestor_chain() -> Result<()> {
1990 let mut layer = sdf::Layer::new_anonymous("anon.usda");
1991 edit_layer(&mut layer, |e| {
1992 sdf::PrimSpec::new(e.data_mut(), "/A/B/C", sdf::Specifier::Def, "Mesh").unwrap();
1993 });
1994
1995 assert_eq!(
1997 layer.prim("/A/B/C")?.and_then(|p| p.specifier()),
1998 Some(sdf::Specifier::Def)
1999 );
2000 assert_eq!(
2001 layer.prim("/A/B")?.and_then(|p| p.specifier()),
2002 Some(sdf::Specifier::Over)
2003 );
2004 assert_eq!(
2005 layer.prim("/A")?.and_then(|p| p.specifier()),
2006 Some(sdf::Specifier::Over)
2007 );
2008 Ok(())
2009 }
2010
2011 #[test]
2012 fn prim_children() -> Result<()> {
2013 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2014 edit_layer(&mut layer, |e| {
2015 sdf::PrimSpec::new(e.data_mut(), "/World", sdf::Specifier::Def, "Xform").unwrap();
2016 sdf::PrimSpec::new(e.data_mut(), "/World/Mesh", sdf::Specifier::Def, "Mesh").unwrap();
2017 sdf::PrimSpec::new(e.data_mut(), "/World/Cube", sdf::Specifier::Def, "Cube").unwrap();
2018 });
2019
2020 let root = layer.pseudo_root().expect("pseudo-root present");
2021 let root_children = root.prim_children().unwrap();
2022 let root_children: Vec<&str> = root_children.iter().map(|t| t.as_str()).collect();
2023 assert_eq!(root_children, ["World"]);
2024
2025 let world = layer.prim("/World")?.expect("prim");
2026 let world_children = world.prim_children().unwrap();
2027 let world_children: Vec<&str> = world_children.iter().map(|t| t.as_str()).collect();
2028 assert_eq!(world_children, ["Mesh", "Cube"]);
2029 Ok(())
2030 }
2031
2032 #[test]
2033 fn property_children() -> Result<()> {
2034 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2035 edit_layer(&mut layer, |e| {
2036 sdf::PrimSpec::new(e.data_mut(), "/Mesh", sdf::Specifier::Def, "Mesh").unwrap();
2037 sdf::AttributeSpec::new(
2038 e.data_mut(),
2039 "/Mesh.points",
2040 "point3f[]",
2041 sdf::Variability::Varying,
2042 false,
2043 )
2044 .unwrap();
2045 sdf::AttributeSpec::new(
2046 e.data_mut(),
2047 "/Mesh.normals",
2048 "normal3f[]",
2049 sdf::Variability::Varying,
2050 false,
2051 )
2052 .unwrap();
2053 sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.material:binding", sdf::Variability::Varying, false)
2054 .unwrap();
2055 });
2056
2057 let mesh = layer.prim("/Mesh")?.expect("prim");
2058 let props = mesh.property_children().unwrap();
2059 let props: Vec<&str> = props.iter().map(|t| t.as_str()).collect();
2060 assert_eq!(props, ["points", "normals", "material:binding"]);
2061 Ok(())
2062 }
2063
2064 #[test]
2065 fn relationship_variability() -> Result<()> {
2066 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2067 edit_layer(&mut layer, |e| {
2068 sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.material:binding", sdf::Variability::Uniform, false)
2069 .unwrap();
2070 let error =
2072 sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.material:binding", sdf::Variability::Varying, false)
2073 .expect_err("a spec already at the path");
2074 assert!(matches!(error, sdf::AuthoringError::InvalidPath { .. }));
2075 sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.varying", sdf::Variability::Varying, false).unwrap();
2076 });
2077
2078 let rel = layer.relationship("/Mesh.material:binding")?.expect("relationship");
2079 assert_eq!(rel.variability(), sdf::Variability::Uniform);
2080 let rel = layer.relationship("/Mesh.varying")?.expect("relationship");
2082 assert_eq!(rel.variability(), sdf::Variability::Varying);
2083 assert!(rel.field(sdf::FieldKey::Variability.as_str()).ok().flatten().is_none());
2084 Ok(())
2085 }
2086
2087 #[test]
2088 fn bad_prim_children_errors() {
2089 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2090 edit_layer(&mut layer, |e| {
2091 e.data_mut().set_field(
2092 &sdf::Path::abs_root(),
2093 sdf::ChildrenKey::PrimChildren.as_str(),
2094 sdf::Value::String("bad".into()),
2095 );
2096 });
2097
2098 let err = layer
2099 .edit(|e| {
2100 sdf::PrimSpec::new(e.data_mut(), "/A", sdf::Specifier::Def, "Xform")?;
2101 Ok(())
2102 })
2103 .unwrap_err();
2104 assert!(matches!(
2105 err,
2106 sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2107 ));
2108
2109 let value = layer
2110 .data()
2111 .get_field(&sdf::Path::abs_root(), sdf::ChildrenKey::PrimChildren.as_str())
2112 .unwrap()
2113 .into_owned();
2114 assert!(matches!(value, sdf::Value::String(value) if value == "bad"));
2115 }
2116
2117 #[test]
2118 fn bad_property_children_errors() -> Result<()> {
2119 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2120 edit_layer(&mut layer, |e| {
2121 sdf::PrimSpec::new(e.data_mut(), "/Mesh", sdf::Specifier::Def, "Mesh").unwrap();
2122 e.data_mut().set_field(
2123 &sdf::path("/Mesh").unwrap(),
2124 sdf::ChildrenKey::PropertyChildren.as_str(),
2125 sdf::Value::String("bad".into()),
2126 );
2127 });
2128
2129 let err = layer
2130 .edit(|e| {
2131 sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.material:binding", sdf::Variability::Varying, false)?;
2132 Ok(())
2133 })
2134 .unwrap_err();
2135 assert!(matches!(
2136 err,
2137 sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2138 ));
2139
2140 assert!(!layer.data().has_spec(&sdf::path("/Mesh.material:binding").unwrap()));
2141 let value = layer
2142 .data()
2143 .get_field(
2144 &sdf::path("/Mesh").unwrap(),
2145 sdf::ChildrenKey::PropertyChildren.as_str(),
2146 )
2147 .unwrap()
2148 .into_owned();
2149 assert!(matches!(value, sdf::Value::String(value) if value == "bad"));
2150 Ok(())
2151 }
2152
2153 #[test]
2154 fn attr_samples() -> Result<()> {
2155 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2156 edit_layer(&mut layer, |e| {
2157 sdf::PrimSpec::new(e.data_mut(), "/Sphere", sdf::Specifier::Def, "Sphere").unwrap();
2158 let mut radius = sdf::AttributeSpec::new(
2159 e.data_mut(),
2160 "/Sphere.radius",
2161 "double",
2162 sdf::Variability::Varying,
2163 false,
2164 )
2165 .unwrap();
2166 radius
2167 .set_default(sdf::Value::Double(2.5))
2168 .expect("value fits the declared type");
2169 radius
2170 .set_time_sample(0.0, sdf::Value::Double(1.0))
2171 .expect("value fits the declared type");
2172 radius
2173 .set_time_sample(10.0, sdf::Value::Double(3.0))
2174 .expect("value fits the declared type");
2175 radius
2177 .set_time_sample(5.0, sdf::Value::Double(2.0))
2178 .expect("value fits the declared type");
2179 });
2180
2181 let read = layer.attribute("/Sphere.radius")?.expect("attr");
2182 assert_eq!(read.type_name(), Some(sdf::ValueTypeName::DOUBLE));
2183 assert_eq!(read.default(), Some(sdf::Value::Double(2.5)));
2184 let samples = read.time_samples().expect("samples authored");
2185 let times: Vec<f64> = samples.iter().map(|(t, _)| *t).collect();
2186 assert_eq!(times, vec![0.0, 5.0, 10.0]);
2187 Ok(())
2188 }
2189
2190 #[test]
2191 fn wrong_ancestor_type() {
2192 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2193 edit_layer(&mut layer, |e| {
2195 e.data_mut()
2196 .create_spec(sdf::path("/A").unwrap(), sdf::SpecType::Attribute);
2197 });
2198
2199 let err = layer
2200 .edit(|e| {
2201 sdf::PrimSpec::new(e.data_mut(), "/A/B", sdf::Specifier::Def, "Xform")?;
2202 Ok(())
2203 })
2204 .unwrap_err();
2205 assert!(matches!(
2206 err,
2207 sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2208 ));
2209 let err = layer
2210 .edit(|e| {
2211 sdf::AttributeSpec::new(e.data_mut(), "/A.x", "double", sdf::Variability::Varying, false)?;
2212 Ok(())
2213 })
2214 .unwrap_err();
2215 assert!(matches!(
2216 err,
2217 sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2218 ));
2219 }
2220
2221 #[test]
2222 fn failed_prim_chain_creation_is_atomic() {
2223 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2224 let bad_child = sdf::path("/A/B").unwrap();
2225 edit_layer(&mut layer, |e| {
2226 e.data_mut().create_spec(bad_child, sdf::SpecType::Attribute);
2227 });
2228
2229 let err = layer
2230 .edit(|e| {
2231 sdf::PrimSpec::new(e.data_mut(), "/A/B", sdf::Specifier::Def, "Xform")?;
2232 Ok(())
2233 })
2234 .unwrap_err();
2235 assert!(matches!(
2236 err,
2237 sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2238 ));
2239
2240 assert!(!layer.data().has_spec(&sdf::path("/A").unwrap()));
2241 assert!(
2242 layer
2243 .data()
2244 .try_field(&sdf::Path::abs_root(), sdf::ChildrenKey::PrimChildren.as_str())
2245 .unwrap()
2246 .is_none()
2247 );
2248 }
2249
2250 #[test]
2251 fn nan_time_sample() -> Result<()> {
2252 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2253 edit_layer(&mut layer, |e| {
2254 sdf::PrimSpec::new(e.data_mut(), "/Sphere", sdf::Specifier::Def, "Sphere").unwrap();
2255 let mut r = sdf::AttributeSpec::new(
2256 e.data_mut(),
2257 "/Sphere.radius",
2258 "double",
2259 sdf::Variability::Varying,
2260 false,
2261 )
2262 .unwrap();
2263 r.set_time_sample(1.0, sdf::Value::Double(1.0))
2264 .expect("value fits the declared type");
2265 r.set_time_sample(f64::NAN, sdf::Value::Double(99.0))
2266 .expect("value fits the declared type");
2267 r.set_time_sample(2.0, sdf::Value::Double(2.0))
2268 .expect("value fits the declared type");
2269 });
2270 let samples = layer
2272 .attribute("/Sphere.radius")?
2273 .unwrap()
2274 .time_samples()
2275 .unwrap()
2276 .clone();
2277 let finite: Vec<f64> = samples.iter().map(|(t, _)| *t).filter(|t| t.is_finite()).collect();
2278 assert_eq!(finite, vec![1.0, 2.0]);
2279
2280 let mut erased = false;
2283 edit_layer(&mut layer, |e| {
2284 erased = e
2285 .attribute_mut("/Sphere.radius")
2286 .unwrap()
2287 .unwrap()
2288 .erase_time_sample(f64::NAN)
2289 .expect("samples readable");
2290 });
2291 assert!(erased);
2292 let times: Vec<f64> = layer
2293 .attribute("/Sphere.radius")?
2294 .unwrap()
2295 .time_samples()
2296 .unwrap()
2297 .iter()
2298 .map(|(t, _)| *t)
2299 .collect();
2300 assert_eq!(times, vec![1.0, 2.0]);
2301 Ok(())
2302 }
2303
2304 #[test]
2305 fn override_prim() -> Result<()> {
2306 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2307 edit_layer(&mut layer, |e| {
2308 sdf::PrimSpec::over(e.data_mut(), "/A/B").unwrap();
2309 });
2310
2311 assert_eq!(
2312 layer.prim("/A")?.and_then(|p| p.specifier()),
2313 Some(sdf::Specifier::Over)
2314 );
2315 assert_eq!(
2316 layer.prim("/A/B")?.and_then(|p| p.specifier()),
2317 Some(sdf::Specifier::Over)
2318 );
2319
2320 edit_layer(&mut layer, |e| {
2322 sdf::PrimSpec::new(e.data_mut(), "/Defined", sdf::Specifier::Def, "Xform").unwrap();
2323 sdf::PrimSpec::over(e.data_mut(), "/Defined").unwrap();
2324 });
2325 assert_eq!(
2326 layer.prim("/Defined")?.and_then(|p| p.specifier()),
2327 Some(sdf::Specifier::Def)
2328 );
2329 Ok(())
2330 }
2331
2332 #[test]
2333 fn pseudo_root_metadata() -> Result<()> {
2334 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2335 edit_layer(&mut layer, |e| {
2336 let mut root = e.pseudo_root_mut().expect("writable");
2337 root.set_default_prim("World");
2338 root.set_documentation("auto-generated");
2339 root.set_start_time_code(1.0);
2340 root.set_end_time_code(24.0);
2341 root.add_sublayer("./over.usda");
2342 root.add_sublayer("./over.usda");
2343 });
2344 let root = layer.pseudo_root().expect("present");
2345 assert_eq!(root.default_prim().as_deref(), Some("World"));
2346 assert_eq!(root.documentation().as_deref(), Some("auto-generated"));
2347 assert_eq!(root.start_time_code(), Some(1.0));
2348 assert_eq!(root.end_time_code(), Some(24.0));
2349 assert_eq!(
2350 root.sublayers(),
2351 Some(vec!["./over.usda".to_string(), "./over.usda".to_string()])
2352 );
2353 Ok(())
2354 }
2355
2356 #[test]
2357 fn invalid_paths() {
2358 let mut layer = sdf::Layer::new_anonymous("anon.usda");
2359 fn invalid(
2361 layer: &mut sdf::Layer,
2362 f: impl FnOnce(&mut sdf::LayerEdit<'_>) -> Result<(), sdf::AuthoringError>,
2363 ) -> bool {
2364 matches!(
2365 layer.edit(f).unwrap_err(),
2366 sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2367 )
2368 }
2369 assert!(invalid(&mut layer, |e| {
2370 sdf::PrimSpec::new(e.data_mut(), "/", sdf::Specifier::Def, "Xform")?;
2371 Ok(())
2372 }));
2373 assert!(invalid(&mut layer, |e| {
2374 sdf::PrimSpec::new(e.data_mut(), "/A.foo", sdf::Specifier::Def, "Xform")?;
2375 Ok(())
2376 }));
2377 assert!(invalid(&mut layer, |e| {
2378 sdf::AttributeSpec::new(e.data_mut(), "/A", "double", sdf::Variability::Varying, false)?;
2379 Ok(())
2380 }));
2381 assert!(invalid(&mut layer, |e| {
2383 sdf::AttributeSpec::new(e.data_mut(), "A.foo", "double", sdf::Variability::Varying, false)?;
2384 Ok(())
2385 }));
2386 assert!(invalid(&mut layer, |e| {
2387 sdf::RelationshipSpec::new(e.data_mut(), "A.foo", sdf::Variability::Varying, false)?;
2388 Ok(())
2389 }));
2390 assert!(matches!(
2393 layer
2394 .edit(|e| {
2395 sdf::AttributeSpec::new(e.data_mut(), "/.foo", "double", sdf::Variability::Varying, false)?;
2396 Ok(())
2397 })
2398 .unwrap_err(),
2399 sdf::EditError::Author(sdf::AuthoringError::Parse(_))
2400 ));
2401 assert!(invalid(&mut layer, |e| {
2404 sdf::AttributeSpec::new(
2405 e.data_mut(),
2406 "/A.rel[/Target].attr",
2407 "double",
2408 sdf::Variability::Varying,
2409 false,
2410 )?;
2411 Ok(())
2412 }));
2413 assert!(invalid(&mut layer, |e| {
2417 sdf::PrimSpec::new(e.data_mut(), "/A{x=}child", sdf::Specifier::Def, "Xform")?;
2418 Ok(())
2419 }));
2420 }
2421
2422 #[test]
2425 fn variant_authoring() -> Result<()> {
2426 let mut layer = sdf::Layer::new_anonymous("variants.usda");
2427 edit_layer(&mut layer, |e| {
2428 sdf::PrimSpec::new(e.data_mut(), "/Prim", sdf::Specifier::Def, "Xform").unwrap();
2429 sdf::PrimSpec::new(e.data_mut(), "/Prim{set=sel}child", sdf::Specifier::Def, "Scope").unwrap();
2430 });
2431
2432 assert_eq!(layer.data().spec_type(&sdf::path("/Prim")?), Some(sdf::SpecType::Prim));
2433 assert_eq!(
2434 layer.data().spec_type(&sdf::path("/Prim{set=}")?),
2435 Some(sdf::SpecType::VariantSet)
2436 );
2437 assert_eq!(
2438 layer.data().spec_type(&sdf::path("/Prim{set=sel}")?),
2439 Some(sdf::SpecType::Variant)
2440 );
2441 assert_eq!(
2442 layer.data().spec_type(&sdf::path("/Prim{set=sel}child")?),
2443 Some(sdf::SpecType::Prim)
2444 );
2445
2446 let token_vec = |path: &str, key: sdf::ChildrenKey| -> Result<Vec<String>> {
2447 match layer.data().get_field(&sdf::path(path)?, key.as_str())?.into_owned() {
2448 sdf::Value::TokenVec(v) => Ok(v.into_iter().map(Into::into).collect()),
2449 other => panic!("expected TokenVec at {path}, got {other:?}"),
2450 }
2451 };
2452 assert_eq!(token_vec("/Prim", sdf::ChildrenKey::VariantSetChildren)?, vec!["set"]);
2453 assert_eq!(
2454 token_vec("/Prim{set=}", sdf::ChildrenKey::VariantChildren)?,
2455 vec!["sel"]
2456 );
2457 assert_eq!(
2458 token_vec("/Prim{set=sel}", sdf::ChildrenKey::PrimChildren)?,
2459 vec!["child"]
2460 );
2461 Ok(())
2462 }
2463
2464 #[test]
2468 fn prim_authoring_rejects_variant_leaf() -> Result<()> {
2469 let mut layer = sdf::Layer::new_anonymous("variants.usda");
2470 edit_layer(&mut layer, |e| {
2471 sdf::PrimSpec::new(e.data_mut(), "/Prim", sdf::Specifier::Def, "Xform").unwrap();
2472 });
2473 assert!(matches!(
2474 layer
2475 .edit(|e| {
2476 sdf::PrimSpec::new(e.data_mut(), "/Prim{set=sel}", sdf::Specifier::Def, "Xform")?;
2477 Ok(())
2478 })
2479 .unwrap_err(),
2480 sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2481 ));
2482 assert!(matches!(
2483 layer
2484 .edit(|e| {
2485 sdf::PrimSpec::over(e.data_mut(), "/Prim{set=sel}")?;
2486 Ok(())
2487 })
2488 .unwrap_err(),
2489 sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2490 ));
2491 assert_eq!(layer.data().spec_type(&sdf::path("/Prim{set=sel}")?), None);
2493 Ok(())
2494 }
2495
2496 #[test]
2497 fn usda_roundtrip() -> Result<()> {
2498 let mut layer = sdf::Layer::new_anonymous("scene.usda");
2499 let material = sdf::path("/World/Material")?;
2500 edit_layer(&mut layer, |e| {
2501 e.pseudo_root_mut().unwrap().set_default_prim("World");
2502 sdf::PrimSpec::new(e.data_mut(), "/World", sdf::Specifier::Def, "Xform").unwrap();
2503 let mut sphere = sdf::PrimSpec::new(e.data_mut(), "/World/Sphere", sdf::Specifier::Def, "Sphere").unwrap();
2504 sphere.set_kind("component");
2505 let mut radius = sdf::AttributeSpec::new(
2506 e.data_mut(),
2507 "/World/Sphere.radius",
2508 "double",
2509 sdf::Variability::Varying,
2510 false,
2511 )
2512 .unwrap();
2513 radius
2514 .set_default(sdf::Value::Double(1.5))
2515 .expect("value fits the declared type");
2516 sdf::PrimSpec::new(e.data_mut(), &material, sdf::Specifier::Def, "Material").unwrap();
2517 let mut binding = sdf::RelationshipSpec::new(
2518 e.data_mut(),
2519 "/World/Sphere.material:binding",
2520 sdf::Variability::Varying,
2521 false,
2522 )
2523 .unwrap();
2524 binding.add_target(material.clone()).unwrap();
2525 let mut surface = sdf::AttributeSpec::new(
2526 e.data_mut(),
2527 "/World/Sphere.inputs:surface",
2528 "token",
2529 sdf::Variability::Varying,
2530 false,
2531 )
2532 .unwrap();
2533 surface
2534 .set_connection_paths([sdf::path("/World/Material.outputs:surface").unwrap()])
2535 .unwrap();
2536 });
2537
2538 let tmp = std::env::temp_dir().join("openusd_authoring_roundtrip.usda");
2539 layer.export(tmp.to_str().unwrap())?;
2540
2541 let parsed = usda::read_file(&tmp)?;
2542 assert_eq!(parsed.spec_type(&sdf::path("/World")?), Some(sdf::SpecType::Prim));
2543 assert_eq!(
2544 parsed.spec_type(&sdf::path("/World/Sphere")?),
2545 Some(sdf::SpecType::Prim)
2546 );
2547 assert_eq!(
2548 parsed.spec_type(&sdf::path("/World/Sphere.radius")?),
2549 Some(sdf::SpecType::Attribute)
2550 );
2551 assert_eq!(
2552 parsed
2553 .get_field(&sdf::Path::abs_root(), sdf::FieldKey::DefaultPrim.as_str())?
2554 .into_owned(),
2555 sdf::Value::Token("World".into())
2556 );
2557 let targets = parsed
2558 .get_field(
2559 &sdf::path("/World/Sphere.material:binding")?,
2560 sdf::FieldKey::TargetPaths.as_str(),
2561 )?
2562 .into_owned()
2563 .try_as_path_list_op()
2564 .expect("relationship targets as PathListOp");
2565 assert!(targets.explicit);
2566 assert_eq!(targets.explicit_items, vec![material]);
2567 let connections = parsed
2568 .get_field(
2569 &sdf::path("/World/Sphere.inputs:surface")?,
2570 sdf::FieldKey::ConnectionPaths.as_str(),
2571 )?
2572 .into_owned()
2573 .try_as_path_list_op()
2574 .expect("connection paths as PathListOp");
2575 assert!(connections.explicit);
2576 assert_eq!(
2577 connections.explicit_items,
2578 vec![sdf::path("/World/Material.outputs:surface")?]
2579 );
2580 let _ = std::fs::remove_file(&tmp);
2581 Ok(())
2582 }
2583
2584 fn opened_through(layer: &mut Layer, open: impl Fn() -> io::Result<Box<dyn ar::Asset>> + 'static) {
2587 layer.resolver = Some(Rc::new(ar::tests::TestResolver(open)));
2588 }
2589
2590 fn edited_fixture(dir: &FsPath) -> Result<(PathBuf, Layer)> {
2593 let path = crate_fixture(dir)?;
2594 let mut layer = Layer::open(path.to_string_lossy())?;
2595 set_component(&mut layer);
2596 Ok((path, layer))
2597 }
2598
2599 fn shared_asset(bytes: Vec<u8>, mapping: bool, sources: &RefCell<Vec<ar::tests::AdviceLog>>) -> Box<dyn ar::Asset> {
2604 let (buffer, log) = ar::tests::recording_source(bytes, mapping);
2605 sources.borrow_mut().push(log);
2606 Box::new(io::Cursor::new(ar::AssetBuffer::Shared(buffer)))
2607 }
2608
2609 fn live_sources(sources: &RefCell<Vec<ar::tests::AdviceLog>>) -> usize {
2611 sources.borrow().iter().filter(|log| Arc::strong_count(log) > 1).count()
2612 }
2613
2614 #[test]
2617 fn save_remaps() -> Result<()> {
2618 let dir = tempfile::tempdir()?;
2619 let (path, mut layer) = edited_fixture(dir.path())?;
2620 let sources = Rc::new(RefCell::new(Vec::new()));
2621 opened_through(&mut layer, {
2622 let (path, sources) = (path.clone(), Rc::clone(&sources));
2623 move || Ok(shared_asset(fs::read(&path)?, true, &sources))
2624 });
2625
2626 layer.save()?;
2627 assert_eq!(sources.borrow().len(), 1, "the saved file is opened once");
2628 assert_eq!(live_sources(&sources), 1, "the layer holds the mapping");
2629 assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2630 let size = layer.data().get_field(&Path::new("/World.size")?, "default")?;
2631 assert_eq!(size.into_owned(), Value::from(vec![1.0_f32; 64]));
2632 Ok(())
2633 }
2634
2635 #[test]
2639 fn save_refuses_stale() -> Result<()> {
2640 let dir = tempfile::tempdir()?;
2641 let (path, mut layer) = edited_fixture(dir.path())?;
2642 let fired = Rc::new(Cell::new(0));
2643 layer.add_sink({
2644 let fired = Rc::clone(&fired);
2645 move |_: &str, _: &ChangeList| fired.set(fired.get() + 1)
2646 });
2647 let before = fs::read(&path)?;
2648 let sources = Rc::new(RefCell::new(Vec::new()));
2649 opened_through(&mut layer, {
2650 let sources = Rc::clone(&sources);
2651 move || Ok(shared_asset(before.clone(), true, &sources))
2652 });
2653
2654 layer.save()?;
2655 assert_eq!(sources.borrow().len(), 1);
2656 assert_eq!(live_sources(&sources), 0, "the stale mapping is let go");
2657 assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2658 assert_eq!(fired.get(), 0, "a save is not a layer change");
2659 assert_eq!(
2660 world_kind(&Layer::open(path.to_string_lossy())?),
2661 Some(tf::Token::from("component"))
2662 );
2663 Ok(())
2664 }
2665
2666 #[test]
2669 fn save_skips_heap() -> Result<()> {
2670 let dir = tempfile::tempdir()?;
2671 let (path, mut layer) = edited_fixture(dir.path())?;
2672 let sources = Rc::new(RefCell::new(Vec::new()));
2673 opened_through(&mut layer, {
2674 let sources = Rc::clone(&sources);
2675 move || Ok(shared_asset(fs::read(&path)?, false, &sources))
2676 });
2677
2678 layer.save()?;
2679 assert_eq!(sources.borrow().len(), 1);
2680 assert_eq!(live_sources(&sources), 0);
2681 assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2682 Ok(())
2683 }
2684
2685 #[test]
2688 fn save_reads_nothing() -> Result<()> {
2689 struct Counting(io::Cursor<Vec<u8>>, Arc<AtomicUsize>);
2691
2692 impl io::Read for Counting {
2693 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
2694 self.1.fetch_add(1, Ordering::Relaxed);
2695 self.0.read(buffer)
2696 }
2697 }
2698
2699 impl io::Seek for Counting {
2700 fn seek(&mut self, position: io::SeekFrom) -> io::Result<u64> {
2701 self.0.seek(position)
2702 }
2703 }
2704
2705 impl ar::Asset for Counting {
2706 fn size(&self) -> io::Result<u64> {
2707 Ok(self.0.get_ref().len() as u64)
2708 }
2709 }
2710
2711 let dir = tempfile::tempdir()?;
2712 let (path, mut layer) = edited_fixture(dir.path())?;
2713 let (opens, reads) = (Rc::new(Cell::new(0)), Arc::new(AtomicUsize::new(0)));
2714 opened_through(&mut layer, {
2715 let (opens, reads) = (Rc::clone(&opens), Arc::clone(&reads));
2716 move || {
2717 opens.set(opens.get() + 1);
2718 Ok(Box::new(Counting(
2719 io::Cursor::new(fs::read(&path)?),
2720 Arc::clone(&reads),
2721 )))
2722 }
2723 });
2724
2725 layer.save()?;
2726 assert_eq!(opens.get(), 1);
2727 assert_eq!(reads.load(Ordering::Relaxed), 0);
2728 assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2729 Ok(())
2730 }
2731
2732 #[test]
2735 fn save_survives_reopen() -> Result<()> {
2736 let dir = tempfile::tempdir()?;
2737 let (path, mut layer) = edited_fixture(dir.path())?;
2738 opened_through(&mut layer, || Err(io::Error::from(io::ErrorKind::PermissionDenied)));
2739
2740 layer.save()?;
2741 let component = Some(tf::Token::from("component"));
2742 assert_eq!(world_kind(&layer), component);
2743 assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
2744 Ok(())
2745 }
2746
2747 #[test]
2750 fn save_replace_failure() -> Result<()> {
2751 let dir = tempfile::tempdir()?;
2752 let (path, mut layer) = edited_fixture(dir.path())?;
2753 let opens = Rc::new(Cell::new(0));
2754 opened_through(&mut layer, {
2755 let opens = Rc::clone(&opens);
2756 move || {
2757 opens.set(opens.get() + 1);
2758 Err(io::Error::from(io::ErrorKind::NotFound))
2759 }
2760 });
2761 fs::remove_file(&path)?;
2762 fs::create_dir(&path)?;
2763
2764 let error = layer.save().expect_err("the replacement fails");
2765 assert!(matches!(error, ExportError::Replace { .. }), "{error:?}");
2766 assert_eq!(opens.get(), 0);
2767 assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2768 Ok(())
2769 }
2770
2771 #[cfg(feature = "mmap")]
2775 #[test]
2776 #[allow(unsafe_code)]
2779 fn save_mapped() -> Result<()> {
2780 let dir = tempfile::tempdir()?;
2781 let path = crate_fixture(dir.path())?;
2782 let resolver = unsafe { ar::DefaultResolver::new().map_files() };
2786 let registry = sdf::LayerRegistry::new(Box::new(resolver));
2787 let identifier = registry.create_identifier(&path.to_string_lossy(), None);
2788 let mut editing = registry.open_layer(&identifier)?.expect("the layer resolves");
2789 let other = registry.open_layer(&identifier)?.expect("the layer resolves");
2790 set_component(&mut editing);
2791
2792 editing.save()?;
2793 let component = Some(tf::Token::from("component"));
2794 assert_eq!(world_kind(&editing), component);
2795 assert_eq!(world_kind(&other), None, "the other view reads the file it mapped");
2796 assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
2797 let size = editing.data().get_field(&Path::new("/World.size")?, "default")?;
2798 assert_eq!(size.into_owned(), Value::from(vec![1.0_f32; 64]));
2799
2800 #[cfg(windows)]
2803 {
2804 let write = || fs::OpenOptions::new().write(true).open(&path);
2805 assert!(write().is_err(), "the saved layer maps its file");
2806 drop(editing);
2807 assert!(write().is_ok(), "the mapping went with the layer");
2808 }
2809 Ok(())
2810 }
2811}