use std::borrow::Cow;
use anyhow::Result;
use crate::{pcp, sdf, tf};
use super::sink::{CommittedChange, PendingChange};
use super::stage::{EditTarget, Stage, StageAuthoringError};
#[derive(Debug, Clone, PartialEq)]
pub struct Diff {
pub edits: Vec<Edit>,
pub mapping: Option<pcp::MapFunction>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Edit {
Create {
path: sdf::Path,
spec_type: sdf::SpecType,
fields: Vec<FieldValue>,
},
SetField {
path: sdf::Path,
spec_type: sdf::SpecType,
field: tf::Token,
value: sdf::Value,
},
EraseField {
path: sdf::Path,
field: tf::Token,
},
RemoveSpec {
path: sdf::Path,
},
}
impl Edit {
pub fn path(&self) -> &sdf::Path {
match self {
Edit::Create { path, .. }
| Edit::SetField { path, .. }
| Edit::EraseField { path, .. }
| Edit::RemoveSpec { path } => path,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldValue {
pub field: tf::Token,
pub value: sdf::Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApplyMode<'a> {
ExactLayer(&'a str),
CurrentEditTarget,
}
pub(super) fn forward_diff(stage: &Stage, objects: &CommittedChange<'_>) -> Result<Vec<Diff>> {
let mapping = objects.provenance.mapping();
let layers = stage.layers();
let mut diffs = Vec::new();
for (layer_identifier, change_list) in objects.layer_changes {
let Some(layer_id) = layers.id_of(layer_identifier) else {
continue;
};
let edits = forward_edits(layers.layer(layer_id).data(), change_list)?;
if !edits.is_empty() {
diffs.push(Diff {
edits,
mapping: mapping.cloned(),
});
}
}
Ok(diffs)
}
fn forward_edits(src: &dyn sdf::AbstractData, change_list: &sdf::ChangeList) -> Result<Vec<Edit>> {
let mut edits = Vec::new();
for (path, entry) in change_list.entries() {
if let Some(spec_type) = src.spec_type(path) {
if entry.is_child_bookkeeping() {
continue;
}
if entry.flags.intersects(sdf::ChangeFlags::ADD) {
edits.push(spec_create_edit(src, path, spec_type)?);
} else {
for field in entry.authored_fields() {
edits.push(field_op(src, path, spec_type, field)?);
}
}
} else if entry.flags.intersects(sdf::ChangeFlags::REMOVE) {
edits.push(Edit::RemoveSpec { path: path.clone() });
}
}
Ok(edits)
}
pub(super) fn inverse_diff(change: &PendingChange<'_>) -> Result<Vec<Edit>> {
let base = change.base;
let mut edits = Vec::new();
for (path, entry) in change.change_list.entries() {
if entry.is_child_bookkeeping() {
continue;
}
let base_type = base.spec_type(path);
if entry.flags.intersects(sdf::ChangeFlags::ADD) {
match base_type {
Some(spec_type) => edits.push(spec_create_edit(base, path, spec_type)?),
None => edits.push(Edit::RemoveSpec { path: path.clone() }),
}
} else if entry.flags.intersects(sdf::ChangeFlags::REMOVE) {
if let Some(spec_type) = base_type {
edits.push(spec_create_edit(base, path, spec_type)?);
}
} else if let Some(spec_type) = base_type {
for field in entry.authored_fields() {
edits.push(field_op(base, path, spec_type, field)?);
}
}
}
edits.sort_by(|a, b| a.path().cmp(b.path()));
Ok(edits)
}
fn spec_create_edit(data: &dyn sdf::AbstractData, path: &sdf::Path, spec_type: sdf::SpecType) -> Result<Edit> {
let mut fields = Vec::new();
for field in data.list_fields(path).unwrap_or_default() {
if sdf::is_children_field(&field) {
continue;
}
if let Some(value) = data.try_field(path, &field)? {
fields.push(FieldValue {
field: tf::Token::from(field),
value: value.into_owned(),
});
}
}
Ok(Edit::Create {
path: path.clone(),
spec_type,
fields,
})
}
fn field_op(
data: &dyn sdf::AbstractData,
path: &sdf::Path,
spec_type: sdf::SpecType,
field: &tf::Token,
) -> Result<Edit> {
Ok(match data.try_field(path, field.as_str())? {
Some(value) => Edit::SetField {
path: path.clone(),
spec_type,
field: field.clone(),
value: value.into_owned(),
},
None => Edit::EraseField {
path: path.clone(),
field: field.clone(),
},
})
}
impl Stage {
pub fn apply_diff(&self, diff: &Diff, mode: ApplyMode<'_>) -> Result<(), StageAuthoringError> {
match mode {
ApplyMode::ExactLayer(identifier) => {
let layer_id = self
.layers()
.id_of(identifier)
.ok_or_else(|| StageAuthoringError::LayerNotFound {
layer: identifier.to_string(),
})?;
self.author_on_layer(layer_id, diff.mapping.as_ref(), |layer| {
apply_edits_verbatim(layer, &diff.edits)
})
}
ApplyMode::CurrentEditTarget => {
let layer_id = self.edit_target_layer_id()?;
let target = self.edit_target();
let translated = translate_diff(diff, &target)?;
self.author_on_layer(layer_id, Some(target.map_function()), |layer| {
apply_translated_diff(layer, diff, &target, &translated)
})
}
}
}
}
pub(super) fn apply_edits_verbatim(layer: &mut sdf::LayerEdit<'_>, edits: &[Edit]) -> Result<(), sdf::AuthoringError> {
for edit in edits {
if !matches!(edit, Edit::RemoveSpec { .. }) {
apply_edit(layer, edit.path(), edit, &sdf::Value::clone)?;
}
}
for edit in edits {
if matches!(edit, Edit::RemoveSpec { .. }) {
apply_edit(layer, edit.path(), edit, &sdf::Value::clone)?;
}
}
Ok(())
}
fn apply_edit(
layer: &mut sdf::LayerEdit<'_>,
dst: &sdf::Path,
edit: &Edit,
translate: &impl Fn(&sdf::Value) -> sdf::Value,
) -> Result<(), sdf::AuthoringError> {
match edit {
Edit::Create { spec_type, fields, .. } => {
let kind_matches = |existing: sdf::SpecType| {
let prim_like = |kind: sdf::SpecType| matches!(kind, sdf::SpecType::Prim | sdf::SpecType::Variant);
existing == *spec_type || (prim_like(existing) && prim_like(*spec_type))
};
match layer.data().spec_type(dst) {
Some(existing) if kind_matches(existing) => {
for field in layer.data().list_fields(dst).unwrap_or_default() {
let recorded = fields.iter().any(|f| f.field == field.as_str());
if !recorded && !sdf::is_children_field(&field) {
layer.data_mut().erase_field(dst, &field);
}
}
}
Some(sdf::SpecType::Attribute | sdf::SpecType::Relationship) => {
layer.remove_spec(dst)?;
}
_ => {}
}
sdf::author_spec(
layer.data_mut(),
dst,
*spec_type,
fields.iter().map(|f| (f.field.as_str(), translate(&f.value))),
)?;
}
Edit::SetField {
spec_type,
field,
value,
..
} => {
sdf::author_spec(layer.data_mut(), dst, *spec_type, [(field.as_str(), translate(value))])?;
}
Edit::EraseField { field, .. } => layer.data_mut().erase_field(dst, field.as_str()),
Edit::RemoveSpec { .. } => remove_or_clear(layer, dst)?,
}
Ok(())
}
fn remove_or_clear(layer: &mut sdf::LayerEdit<'_>, path: &sdf::Path) -> Result<(), sdf::AuthoringError> {
if layer.remove_spec(path)? || layer.data().spec_type(path).is_none() {
return Ok(());
}
for field in layer.data().list_fields(path).unwrap_or_default() {
if !sdf::is_children_field(&field) {
layer.data_mut().erase_field(path, &field);
}
}
Ok(())
}
struct TranslatedDiff<'a> {
edits: Vec<(sdf::Path, &'a Edit)>,
removed: Vec<sdf::Path>,
time_offset: sdf::LayerOffset,
}
fn lift_path<'a>(mapping: Option<&pcp::MapFunction>, path: &'a sdf::Path) -> Option<Cow<'a, sdf::Path>> {
match mapping {
Some(m) => m.map_source_to_target(path).map(Cow::Owned),
None => Some(Cow::Borrowed(path)),
}
}
fn translate_diff<'a>(diff: &'a Diff, target: &EditTarget) -> Result<TranslatedDiff<'a>, StageAuthoringError> {
let translate = |path: &sdf::Path| -> Result<sdf::Path, StageAuthoringError> {
lift_path(diff.mapping.as_ref(), path)
.and_then(|scene| target.map_to_spec_path(&scene))
.ok_or_else(|| StageAuthoringError::OutsideEditTarget { path: path.clone() })
};
let mut edits = Vec::new();
let mut removed = Vec::new();
for edit in &diff.edits {
let dst = translate(edit.path())?;
match edit {
Edit::RemoveSpec { .. } => {
removed.push(dst);
continue;
}
Edit::Create { fields, .. } => {
for field in fields {
validate_value(diff, target, &field.value)?;
}
}
Edit::SetField { value, .. } => validate_value(diff, target, value)?,
Edit::EraseField { .. } => {}
}
edits.push((dst, edit));
}
edits.sort_by(|(dst_a, edit_a), (dst_b, edit_b)| {
let direct = |edit: &Edit| !edit.path().contains_prim_variant_selection();
dst_a.cmp(dst_b).then_with(|| direct(edit_a).cmp(&direct(edit_b)))
});
let lift_offset = diff
.mapping
.as_ref()
.map_or(sdf::LayerOffset::IDENTITY, |m| m.time_offset());
let time_offset = target.map_function().time_offset().inverse().concatenate(&lift_offset);
Ok(TranslatedDiff {
edits,
removed,
time_offset,
})
}
fn apply_translated_diff(
layer: &mut sdf::LayerEdit<'_>,
diff: &Diff,
target: &EditTarget,
translated: &TranslatedDiff<'_>,
) -> Result<(), sdf::AuthoringError> {
let translate = |value: &sdf::Value| {
translate_value(value, diff, target, translated.time_offset).unwrap_or_else(|| value.clone())
};
for (dst, edit) in &translated.edits {
apply_edit(layer, dst, edit, &translate)?;
}
for path in &translated.removed {
remove_or_clear(layer, path)?;
}
Ok(())
}
fn validate_value(diff: &Diff, target: &EditTarget, value: &sdf::Value) -> Result<(), StageAuthoringError> {
if let sdf::Value::TimeSamples(samples) = value {
for (_, sample) in samples {
validate_value(diff, target, sample)?;
}
return Ok(());
}
if !value.has_embedded_paths() {
return Ok(());
}
let failed: std::cell::Cell<Option<sdf::Path>> = std::cell::Cell::new(None);
value.remap_paths(|path| {
if translate_value_path(diff, target, path).is_none() {
failed.set(Some(path.clone()));
}
path.clone()
});
match failed.take() {
Some(path) => Err(StageAuthoringError::OutsideEditTarget { path }),
None => Ok(()),
}
}
fn translate_value_path(diff: &Diff, target: &EditTarget, path: &sdf::Path) -> Option<sdf::Path> {
lift_path(diff.mapping.as_ref(), path).and_then(|scene| target.map_to_spec_target_path(&scene))
}
fn translate_value(
value: &sdf::Value,
diff: &Diff,
target: &EditTarget,
time_offset: sdf::LayerOffset,
) -> Option<sdf::Value> {
if let sdf::Value::TimeSamples(samples) = value {
let retime = !time_offset.is_identity();
if !retime && !samples.iter().any(|(_, sample)| sample.has_embedded_paths()) {
return None;
}
return Some(sdf::Value::TimeSamples(
samples
.iter()
.map(|(time, sample)| {
let time = if retime { time_offset.apply(*time) } else { *time };
let sample = translate_value(sample, diff, target, time_offset).unwrap_or_else(|| sample.clone());
(time, sample)
})
.collect(),
));
}
if !value.has_embedded_paths() {
return None;
}
Some(value.remap_paths(|path| translate_value_path(diff, target, path).unwrap_or_else(|| path.clone())))
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use anyhow::Result;
use super::super::sink::Provenance;
use super::*;
use crate::usd::{EditTargetArc, TimeCode};
fn in_memory_stage() -> Result<Stage> {
Stage::builder().in_memory("anon.usda")
}
fn fixture_path(relative: &str) -> String {
format!("{}/fixtures/{relative}", env!("CARGO_MANIFEST_DIR"))
}
#[test]
fn forward_removal() -> Result<()> {
let stage = in_memory_stage()?;
stage.define_prim("/World")?;
stage.create_attribute("/World.size", "double")?;
let size = sdf::path("/World.size")?;
let root = stage.root_layer().identifier().to_string();
stage.remove_property(size.clone())?;
let mut change_list = sdf::ChangeList::new();
change_list.entry_mut(&size).flags |= sdf::ChangeFlags::REMOVE_PROPERTY;
let provenance = Provenance::LocalStack;
let layer_changes = [(root.clone(), change_list.clone())];
let change = CommittedChange {
resynced: &[],
changed_info_only: std::slice::from_ref(&size),
layer_identifier: &root,
change_list: &change_list,
layer_changes: &layer_changes,
provenance: &provenance,
generation: 0,
};
let diffs = forward_diff(&stage, &change)?;
assert_eq!(diffs.len(), 1);
assert_eq!(diffs[0].edits, vec![Edit::RemoveSpec { path: size.clone() }]);
Ok(())
}
#[test]
fn forward_set_value() -> Result<()> {
let stage = in_memory_stage()?;
stage.define_prim("/World")?;
let attr = stage.create_attribute("/World.size", "double")?;
let value: Rc<RefCell<Option<f64>>> = Rc::new(RefCell::new(None));
let _id = {
let value = value.clone();
let size = sdf::Path::new("/World.size")?;
stage.add_sink(move |stage: &Stage, change: &CommittedChange<'_>| {
let diffs = forward_diff(stage, change).expect("forward diff");
value.replace(diffs.iter().flat_map(|d| &d.edits).find_map(|e| match e {
Edit::SetField { path, field, value, .. }
if *path == size && field == sdf::FieldKey::Default.as_str() =>
{
value.clone().try_as_double()
}
_ => None,
}));
})
};
attr.set(2.0_f64)?;
assert_eq!(*value.borrow(), Some(2.0));
Ok(())
}
#[test]
fn forward_field_erasure() -> Result<()> {
let stage = in_memory_stage()?;
stage.define_prim("/World")?;
stage.create_attribute("/World.target", "double")?;
let attr = stage.create_attribute("/World.size", "double")?;
let attr = attr.set_connections([sdf::Path::new("/World.target")?])?;
let erased: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
let _id = {
let erased = erased.clone();
stage.add_sink(move |stage: &Stage, change: &CommittedChange<'_>| {
let diffs = forward_diff(stage, change).expect("forward diff");
let size = sdf::Path::new("/World.size").unwrap();
erased
.borrow_mut()
.extend(diffs.iter().flat_map(|d| &d.edits).filter_map(|e| match e {
Edit::EraseField { path, field } if *path == size => Some(field.as_str().to_string()),
_ => None,
}));
})
};
attr.clear_connections()?;
assert!(erased.borrow().iter().any(|f| f == "connectionPaths"));
Ok(())
}
fn capture_diffs(stage: &Stage) -> Rc<RefCell<Vec<Diff>>> {
let diffs: Rc<RefCell<Vec<Diff>>> = Rc::new(RefCell::new(Vec::new()));
{
let diffs = diffs.clone();
stage.add_sink(move |stage: &Stage, change: &CommittedChange<'_>| {
diffs
.borrow_mut()
.extend(forward_diff(stage, change).expect("forward diff"));
});
}
diffs
}
#[test]
fn apply_diff_roundtrip() -> Result<()> {
let a = in_memory_stage()?;
let diffs = capture_diffs(&a);
a.define_prim("/World")?.set_type_name("Xform")?;
a.define_prim("/World/Mesh")?.set_type_name("Mesh")?;
a.create_attribute("/World/Mesh.size", "double")?.set(2.0_f64)?;
let b = in_memory_stage()?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
assert_eq!(b.prim("/World").child_names()?, vec![tf::Token::new("Mesh")]);
assert_eq!(b.prim("/World").type_name()?.as_deref(), Some("Xform"));
assert_eq!(b.attribute("/World/Mesh.size").get::<f64>()?, Some(2.0));
Ok(())
}
#[test]
fn apply_diff_removal() -> Result<()> {
let a = in_memory_stage()?;
let diffs = capture_diffs(&a);
a.define_prim("/World")?;
a.define_prim("/World/Doomed")?;
a.define_prim("/World/Target")?;
let attr = a.create_attribute("/World.size", "double")?;
let attr = attr.set_connections([sdf::Path::new("/World/Target.size")?])?;
assert!(a.remove_prim("/World/Doomed")?);
attr.clear_connections()?;
let edits: Vec<Edit> = diffs.borrow().iter().flat_map(|d| d.edits.clone()).collect();
let doomed = sdf::path("/World/Doomed")?;
assert!(edits.iter().any(|e| *e == Edit::RemoveSpec { path: doomed.clone() }));
let size = sdf::path("/World.size")?;
assert!(edits
.iter()
.any(|e| matches!(e, Edit::EraseField { path, .. } if *path == size)));
let b = in_memory_stage()?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
assert!(!b.prim("/World/Doomed").is_valid()?);
assert!(b.prim("/World/Target").is_valid()?);
assert!(b.attribute(&sdf::path("/World.size")?).connections()?.is_empty());
Ok(())
}
#[test]
fn apply_diff_into_variant() -> Result<()> {
let a = in_memory_stage()?;
let diffs = capture_diffs(&a);
a.define_prim("/Prim")?.set_type_name("Xform")?;
a.define_prim("/Prim/child")?;
a.create_attribute("/Prim/child.out", "double")?
.set_connections([sdf::path("/Prim/other.in")?])?;
let b = in_memory_stage()?;
let root = b.edit_target().layer_identifier().to_string();
b.set_edit_target(EditTarget::for_local_direct_variant(root, sdf::path("/Prim{set=sel}")?))?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
let layer = b.root_layer();
let data = layer.data();
assert_eq!(
data.spec_type(&sdf::path("/Prim{set=sel}child")?),
Some(sdf::SpecType::Prim)
);
assert_eq!(
data.spec_type(&sdf::path("/Prim{set=sel}child.out")?),
Some(sdf::SpecType::Attribute)
);
assert!(!data.has_spec(&sdf::path("/Prim/child")?));
let type_name = data
.try_field(&sdf::path("/Prim{set=sel}")?, sdf::FieldKey::TypeName.as_str())?
.expect("typeName authored on the variant spec")
.into_owned()
.try_as_token()
.expect("typeName is a token");
assert_eq!(type_name.as_str(), "Xform");
let connections = data
.try_field(
&sdf::path("/Prim{set=sel}child.out")?,
sdf::FieldKey::ConnectionPaths.as_str(),
)?
.expect("connections authored")
.into_owned()
.try_as_path_list_op()
.expect("connections are a path list op");
assert_eq!(connections.explicit_items, vec![sdf::path("/Prim/other.in")?]);
Ok(())
}
#[test]
fn apply_diff_into_reference_arc() -> Result<()> {
let a = Stage::open(&fixture_path("ref_external.usda"))?;
let diffs = capture_diffs(&a);
a.define_prim("/World/MyPrim/added")?;
a.create_attribute("/World/MyPrim/added.out", "double")?
.set_connections([sdf::path("/World/MyPrim/Child.in")?])?;
let b = Stage::open(&fixture_path("ref_external.usda"))?;
let target = b.edit_target_for_node(&sdf::path("/World/MyPrim")?, EditTargetArc::Reference)?;
let _ctx = b.edit_context(target)?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
assert!(!b.root_layer().data().has_spec(&sdf::path("/World/MyPrim/added")?));
assert!(b.prim("/World/MyPrim/added").is_valid()?);
assert_eq!(
b.attribute(&sdf::path("/World/MyPrim/added.out")?).connections()?,
vec![sdf::path("/World/MyPrim/Child.in")?]
);
Ok(())
}
#[test]
fn apply_diff_lifts_variant_diff() -> Result<()> {
let a = in_memory_stage()?;
a.define_prim("/Prim")?;
let root_a = a.edit_target().layer_identifier().to_string();
a.set_edit_target(EditTarget::for_local_direct_variant(
root_a,
sdf::path("/Prim{set=sel}")?,
))?;
let diffs = capture_diffs(&a);
a.define_prim("/Prim/child")?.set_type_name("Scope")?;
let b = in_memory_stage()?;
let root_b = b.edit_target().layer_identifier().to_string();
b.set_edit_target(EditTarget::for_local_direct_variant(
root_b,
sdf::path("/Prim{mirror=m}")?,
))?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
let layer = b.root_layer();
let data = layer.data();
assert_eq!(
data.spec_type(&sdf::path("/Prim{mirror=m}child")?),
Some(sdf::SpecType::Prim)
);
assert!(!data.has_spec(&sdf::path("/Prim{set=sel}child")?));
let type_name = data
.try_field(&sdf::path("/Prim{mirror=m}child")?, sdf::FieldKey::TypeName.as_str())?
.expect("typeName carried into the consumer's variant")
.into_owned()
.try_as_token()
.expect("typeName is a token");
assert_eq!(type_name.as_str(), "Scope");
Ok(())
}
#[test]
fn apply_diff_transferred() -> Result<()> {
let a = in_memory_stage()?;
a.define_prim("/Prim")?;
let root_a = a.edit_target().layer_identifier().to_string();
a.set_edit_target(EditTarget::for_local_direct_variant(
root_a,
sdf::path("/Prim{set=sel}")?,
))?;
let diffs = capture_diffs(&a);
a.define_prim("/Prim/child")?.set_type_name("Scope")?;
let mut wire = Vec::new();
for diff in diffs.borrow().iter() {
let mapping = diff.mapping.as_ref().map(|m| {
let pairs: Vec<(String, String)> = m
.path_pairs()
.iter()
.map(|(s, t)| (s.as_str().to_owned(), t.as_str().to_owned()))
.collect();
(pairs, m.has_root_identity(), m.time_offset())
});
wire.push((diff.edits.clone(), mapping));
}
let b = in_memory_stage()?;
let root_b = b.edit_target().layer_identifier().to_string();
b.set_edit_target(EditTarget::for_local_direct_variant(
root_b,
sdf::path("/Prim{mirror=m}")?,
))?;
for (edits, mapping) in &wire {
let mapping = match mapping {
Some((pairs, root_identity, offset)) => {
let pairs = pairs
.iter()
.map(|(s, t)| Ok((sdf::path(s)?, sdf::path(t)?)))
.collect::<Result<Vec<_>>>()?;
let mut mapping = pcp::MapFunction::new(pairs);
if *root_identity {
mapping = mapping.with_root_identity();
}
Some(mapping.with_time_offset(*offset))
}
None => None,
};
b.apply_diff(
&Diff {
edits: edits.clone(),
mapping,
},
ApplyMode::CurrentEditTarget,
)?;
}
let data_b = b.root_layer();
assert_eq!(
data_b.data().spec_type(&sdf::path("/Prim{mirror=m}child")?),
Some(sdf::SpecType::Prim)
);
Ok(())
}
#[test]
fn apply_diff_exact_layer() -> Result<()> {
let a = in_memory_stage()?;
a.define_prim("/Prim")?;
let root = a.edit_target().layer_identifier().to_string();
a.set_edit_target(EditTarget::for_local_direct_variant(root, sdf::path("/Prim{set=sel}")?))?;
let diffs = capture_diffs(&a);
a.define_prim("/Prim/child")?;
let b = in_memory_stage()?;
let root_b = b.root_layer().identifier().to_string();
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::ExactLayer(&root_b))?;
}
assert_eq!(
b.root_layer().data().spec_type(&sdf::path("/Prim{set=sel}child")?),
Some(sdf::SpecType::Prim)
);
assert!(matches!(
b.apply_diff(&diffs.borrow()[0], ApplyMode::ExactLayer("not-a-layer.usda")),
Err(StageAuthoringError::LayerNotFound { .. })
));
Ok(())
}
#[test]
fn apply_diff_outside_arc() -> Result<()> {
let a = Stage::open(&fixture_path("ref_external.usda"))?;
let diffs = capture_diffs(&a);
a.define_prim("/Elsewhere")?;
let b = Stage::open(&fixture_path("ref_external.usda"))?;
let target = b.edit_target_for_node(&sdf::path("/World/MyPrim")?, EditTargetArc::Reference)?;
let _ctx = b.edit_context(target)?;
assert!(matches!(
b.apply_diff(&diffs.borrow()[0], ApplyMode::CurrentEditTarget),
Err(StageAuthoringError::OutsideEditTarget { .. })
));
assert!(!b.prim("/Elsewhere").is_valid()?);
Ok(())
}
#[test]
fn apply_diff_keeps_bare_over() -> Result<()> {
let a = Stage::open(&fixture_path("ref_external.usda"))?;
let diffs = capture_diffs(&a);
a.override_prim("/World/MyPrim/bare")?;
let b = Stage::open(&fixture_path("ref_external.usda"))?;
let target = b.edit_target_for_node(&sdf::path("/World/MyPrim")?, EditTargetArc::Reference)?;
let _ctx = b.edit_context(target)?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
assert!(!b.root_layer().data().has_spec(&sdf::path("/World/MyPrim/bare")?));
assert!(b.prim("/World/MyPrim/bare").is_valid()?);
Ok(())
}
#[test]
fn apply_diff_retimes_samples() -> Result<()> {
let reference_stage = || -> Result<Stage> {
let stage = in_memory_stage()?;
stage.define_prim("/Source")?.create_attribute("x", "double")?;
stage.define_prim("/Prim")?.set_metadata(
sdf::FieldKey::References.as_str(),
sdf::Value::ReferenceListOp(sdf::ReferenceListOp::prepended([sdf::Reference {
prim_path: sdf::path("/Source")?,
layer_offset: sdf::LayerOffset::new(10.0, 1.0),
..Default::default()
}])),
)?;
Ok(stage)
};
let a = reference_stage()?;
let diffs = capture_diffs(&a);
{
let target = a.edit_target_for_node(&sdf::path("/Prim")?, EditTargetArc::Reference)?;
let _ctx = a.edit_context(target)?;
a.attribute("/Prim.x")
.set_at(sdf::Value::Double(42.0), TimeCode::new(15.0))?;
}
let b = reference_stage()?;
b.create_attribute("/Prim.x", "double")?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
let local = b.attribute("/Prim.x").time_samples()?.expect("samples");
assert_eq!(local, vec![(15.0, sdf::Value::Double(42.0))]);
let c = reference_stage()?;
{
let target = c.edit_target_for_node(&sdf::path("/Prim")?, EditTargetArc::Reference)?;
let _ctx = c.edit_context(target)?;
for diff in diffs.borrow().iter() {
c.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
}
let source = c.attribute("/Source.x").time_samples()?.expect("samples");
assert_eq!(source, vec![(5.0, sdf::Value::Double(42.0))]);
assert_eq!(c.attribute("/Prim.x").get_at::<f64>(TimeCode::new(15.0))?, Some(42.0));
Ok(())
}
#[test]
fn apply_diff_atomic() -> Result<()> {
let stage = in_memory_stage()?;
let diff = Diff {
edits: vec![
Edit::Create {
path: sdf::path("/A")?,
spec_type: sdf::SpecType::Prim,
fields: Vec::new(),
},
Edit::Create {
path: sdf::path("/B.x")?,
spec_type: sdf::SpecType::Prim,
fields: Vec::new(),
},
],
mapping: None,
};
assert!(matches!(
stage.apply_diff(&diff, ApplyMode::CurrentEditTarget),
Err(StageAuthoringError::Layer(_))
));
assert!(!stage.root_layer().data().has_spec(&sdf::path("/A")?));
Ok(())
}
#[test]
fn apply_diff_attr_needs_type() -> Result<()> {
let a = in_memory_stage()?;
a.create_attribute("/Prim.x", "double")?;
let diffs = capture_diffs(&a);
a.attribute("/Prim.x").set(2.0_f64)?;
let b = in_memory_stage()?;
assert!(matches!(
b.apply_diff(&diffs.borrow()[0], ApplyMode::CurrentEditTarget),
Err(StageAuthoringError::Layer(_))
));
assert!(!b.root_layer().data().has_spec(&sdf::path("/Prim.x")?));
Ok(())
}
#[test]
fn apply_diff_removes_variant_opinions() -> Result<()> {
let a = in_memory_stage()?;
let diffs = capture_diffs(&a);
a.define_prim("/Prim")?.set_type_name("Xform")?;
a.define_prim("/Prim/child")?;
let b = in_memory_stage()?;
let root = b.edit_target().layer_identifier().to_string();
b.set_edit_target(EditTarget::for_local_direct_variant(root, sdf::path("/Prim{set=sel}")?))?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
let variant = sdf::path("/Prim{set=sel}")?;
let type_name = sdf::FieldKey::TypeName.as_str();
assert!(b.root_layer().data().try_field(&variant, type_name)?.is_some());
let replayed = diffs.borrow().len();
a.remove_prim("/Prim")?;
for diff in diffs.borrow().iter().skip(replayed) {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
let layer = b.root_layer();
let data = layer.data();
assert!(data.try_field(&variant, type_name)?.is_none());
assert!(!data.has_spec(&sdf::path("/Prim{set=sel}child")?));
Ok(())
}
#[test]
fn apply_diff_value_outside_arc() -> Result<()> {
let a = Stage::open(&fixture_path("ref_external.usda"))?;
let diffs = capture_diffs(&a);
a.create_attribute("/World/MyPrim/added.out", "double")?
.set_connections([sdf::path("/Elsewhere.in")?])?;
let b = Stage::open(&fixture_path("ref_external.usda"))?;
let target = b.edit_target_for_node(&sdf::path("/World/MyPrim")?, EditTargetArc::Reference)?;
let _ctx = b.edit_context(target)?;
let replay = diffs
.borrow()
.iter()
.try_for_each(|diff| b.apply_diff(diff, ApplyMode::CurrentEditTarget));
assert!(matches!(replay, Err(StageAuthoringError::OutsideEditTarget { .. })));
Ok(())
}
#[test]
fn apply_diff_create_replaces() -> Result<()> {
let a = in_memory_stage()?;
let diffs = capture_diffs(&a);
a.define_prim("/Prim")?;
let b = in_memory_stage()?;
b.define_prim("/Prim")?.set_kind("component")?;
let root = b.root_layer().identifier().to_string();
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::ExactLayer(&root))?;
}
assert_eq!(b.prim("/Prim").kind()?, None);
Ok(())
}
#[test]
fn apply_diff_create_replaces_variant() -> Result<()> {
let a = in_memory_stage()?;
let diffs = capture_diffs(&a);
a.define_prim("/Prim")?.set_kind("component")?;
let b = in_memory_stage()?;
let root = b.edit_target().layer_identifier().to_string();
b.set_edit_target(EditTarget::for_local_direct_variant(root, sdf::path("/Prim{set=sel}")?))?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
let variant = sdf::path("/Prim{set=sel}")?;
let kind = sdf::FieldKey::Kind.as_str();
assert!(b.root_layer().data().try_field(&variant, kind)?.is_some());
let fresh = in_memory_stage()?;
let fresh_diffs = capture_diffs(&fresh);
fresh.define_prim("/Prim")?;
for diff in fresh_diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
assert!(b.root_layer().data().try_field(&variant, kind)?.is_none());
Ok(())
}
#[test]
fn apply_diff_create_replaces_kind() -> Result<()> {
let a = in_memory_stage()?;
a.define_prim("/Prim")?;
let diffs = capture_diffs(&a);
a.create_attribute("/Prim.x", "double")?;
let b = in_memory_stage()?;
b.define_prim("/Prim")?;
b.create_relationship("/Prim.x")?;
let root = b.root_layer().identifier().to_string();
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::ExactLayer(&root))?;
}
assert_eq!(
b.root_layer().data().spec_type(&sdf::path("/Prim.x")?),
Some(sdf::SpecType::Attribute)
);
Ok(())
}
#[test]
fn apply_diff_merges_fields() -> Result<()> {
let a = in_memory_stage()?;
a.define_prim("/Prim")?.set_type_name("Xform")?;
let diffs = capture_diffs(&a);
a.prim("/Prim").set_kind("component")?;
let b = in_memory_stage()?;
b.define_prim("/Prim")?.set_type_name("Scope")?;
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::CurrentEditTarget)?;
}
assert_eq!(b.prim("/Prim").kind()?.as_deref(), Some("component"));
assert_eq!(b.prim("/Prim").type_name()?.as_deref(), Some("Scope"));
let c = in_memory_stage()?;
c.define_prim("/Prim")?.set_type_name("Scope")?;
let root_c = c.root_layer().identifier().to_string();
for diff in diffs.borrow().iter() {
c.apply_diff(diff, ApplyMode::ExactLayer(&root_c))?;
}
assert_eq!(c.prim("/Prim").kind()?.as_deref(), Some("component"));
assert_eq!(c.prim("/Prim").type_name()?.as_deref(), Some("Scope"));
Ok(())
}
#[test]
fn apply_diff_exact_notice_paths() -> Result<()> {
let a = in_memory_stage()?;
a.define_prim("/Prim")?;
let root_a = a.edit_target().layer_identifier().to_string();
a.set_edit_target(EditTarget::for_local_direct_variant(
root_a,
sdf::path("/Prim{set=sel}")?,
))?;
let diffs = capture_diffs(&a);
a.define_prim("/Prim/child")?;
let b = in_memory_stage()?;
let root_b = b.root_layer().identifier().to_string();
let resynced: Rc<RefCell<Vec<sdf::Path>>> = Rc::new(RefCell::new(Vec::new()));
let _id = {
let resynced = resynced.clone();
b.add_sink(move |_: &Stage, change: &CommittedChange<'_>| {
resynced.borrow_mut().extend(change.resynced.iter().cloned());
})
};
for diff in diffs.borrow().iter() {
b.apply_diff(diff, ApplyMode::ExactLayer(&root_b))?;
}
let resynced = resynced.borrow();
assert!(resynced.contains(&sdf::path("/Prim/child")?));
assert!(!resynced.contains(&sdf::path("/Prim{set=sel}child")?));
Ok(())
}
#[test]
fn apply_diff_author_error() -> Result<()> {
let stage = in_memory_stage()?;
let root = stage.root_layer().identifier().to_string();
let diff = Diff {
edits: vec![Edit::Create {
path: sdf::Path::abs_root(),
spec_type: sdf::SpecType::Prim,
fields: Vec::new(),
}],
mapping: None,
};
assert!(matches!(
stage.apply_diff(&diff, ApplyMode::ExactLayer(&root)),
Err(StageAuthoringError::Layer(_))
));
Ok(())
}
}