use crate::sdf;
use crate::tf;
pub fn copy_spec(
src: &dyn sdf::AbstractData,
src_path: &sdf::Path,
dst: &mut dyn sdf::AbstractData,
dst_path: &sdf::Path,
) -> Result<bool, sdf::AuthoringError> {
match src.spec_type(src_path) {
Some(spec_type) if is_copyable(spec_type) => {}
_ => return Ok(false),
}
sdf::spec::remove_spec(dst, dst_path)?;
copy_spec_with(
src,
src_path,
dst,
dst_path,
|args| should_copy_value(args, src_path, dst_path),
should_copy_children,
)?;
Ok(true)
}
pub fn copy_spec_within(
data: &mut dyn sdf::AbstractData,
src_path: &sdf::Path,
dst_path: &sdf::Path,
) -> Result<bool, sdf::AuthoringError> {
let mut scratch = sdf::Data::new();
if !copy_spec(data, src_path, &mut scratch, src_path)? {
return Ok(false);
}
copy_spec(&scratch, src_path, data, dst_path)?;
Ok(true)
}
pub fn copy_spec_with<V, C>(
src: &dyn sdf::AbstractData,
src_path: &sdf::Path,
dst: &mut dyn sdf::AbstractData,
dst_path: &sdf::Path,
should_copy_value: V,
should_copy_children: C,
) -> Result<(), sdf::AuthoringError>
where
V: FnMut(CopyValueArgs<'_>) -> CopyValue,
C: FnMut(CopyChildrenArgs<'_>) -> CopyChildren,
{
CopyOp {
src,
dst,
value_policy: should_copy_value,
children_policy: should_copy_children,
}
.copy_into(src_path, dst_path)
}
pub fn should_copy_value(args: CopyValueArgs, src_root: &sdf::Path, dst_root: &sdf::Path) -> CopyValue {
if args.value.has_embedded_paths() {
CopyValue::Replace(
args.value
.remap_paths(|path| path.replace_prefix(src_root, dst_root).unwrap_or_else(|| path.clone())),
)
} else {
CopyValue::Copy
}
}
pub fn should_copy_children(_args: CopyChildrenArgs) -> CopyChildren {
CopyChildren::All
}
pub enum CopyValue {
Copy,
Skip,
Replace(sdf::Value),
}
pub enum CopyChildren {
All,
Skip,
Map(Vec<(tf::Token, tf::Token)>),
}
pub struct CopyValueArgs<'a> {
pub spec_type: sdf::SpecType,
pub field: &'a str,
pub value: &'a sdf::Value,
pub src_path: &'a sdf::Path,
pub dst_path: &'a sdf::Path,
}
pub struct CopyChildrenArgs<'a> {
pub children_field: sdf::ChildrenKey,
pub children: &'a [tf::Token],
pub src_path: &'a sdf::Path,
pub dst_path: &'a sdf::Path,
}
struct CopyOp<'a, V, C> {
src: &'a dyn sdf::AbstractData,
dst: &'a mut dyn sdf::AbstractData,
value_policy: V,
children_policy: C,
}
impl<V, C> CopyOp<'_, V, C>
where
V: FnMut(CopyValueArgs<'_>) -> CopyValue,
C: FnMut(CopyChildrenArgs<'_>) -> CopyChildren,
{
fn copy_into(&mut self, src_path: &sdf::Path, dst_path: &sdf::Path) -> Result<(), sdf::AuthoringError> {
let Some(spec_type) = self.src.spec_type(src_path) else {
return Ok(());
};
if !is_copyable(spec_type) {
return Ok(());
}
create_dst_spec(self.dst, dst_path, spec_type)?;
let fields = self.src.list_fields(src_path).unwrap_or_default();
let mut authored_type_name = false;
for field in &fields {
if is_children_field(field) {
continue;
}
let Some(value) = self.src.try_field(src_path, field)? else {
continue;
};
let args = CopyValueArgs {
spec_type,
field,
value: &value,
src_path,
dst_path,
};
let authored = match (self.value_policy)(args) {
CopyValue::Skip => false,
CopyValue::Copy => {
self.dst.set_field(dst_path, field, value.into_owned());
true
}
CopyValue::Replace(value) => {
self.dst.set_field(dst_path, field, value);
true
}
};
authored_type_name |= authored && field == sdf::FieldKey::TypeName.as_str();
}
if spec_type == sdf::SpecType::Attribute && !authored_type_name {
self.dst.erase_field(dst_path, sdf::FieldKey::TypeName.as_str());
}
for &key in child_fields(spec_type) {
let names = read_child_names(self.src, src_path, key)?;
if names.is_empty() {
continue;
}
let args = CopyChildrenArgs {
children_field: key,
children: &names,
src_path,
dst_path,
};
match (self.children_policy)(args) {
CopyChildren::Skip => {}
CopyChildren::All => {
for name in &names {
self.copy_child(src_path, dst_path, key, name, name)?;
}
}
CopyChildren::Map(mapping) => {
for (src_name, dst_name) in &mapping {
self.copy_child(src_path, dst_path, key, src_name, dst_name)?;
}
}
}
}
Ok(())
}
fn copy_child(
&mut self,
src_path: &sdf::Path,
dst_path: &sdf::Path,
key: sdf::ChildrenKey,
src_name: &str,
dst_name: &str,
) -> Result<(), sdf::AuthoringError> {
let child_src = join_child(src_path, key, src_name)?;
let child_dst = join_child(dst_path, key, dst_name)?;
self.copy_into(&child_src, &child_dst)
}
}
pub(crate) fn author_spec<'a>(
dst: &mut dyn sdf::AbstractData,
path: &sdf::Path,
spec_type: sdf::SpecType,
fields: impl IntoIterator<Item = (&'a str, sdf::Value)>,
) -> Result<(), sdf::AuthoringError> {
if !is_copyable(spec_type) {
return Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "spec kind has no own storage to author",
});
}
let exists = dst.spec_type(path) == Some(spec_type);
if !exists {
create_dst_spec(dst, path, spec_type)?;
}
let mut authored_type_name = false;
for (field, value) in fields {
authored_type_name |= field == sdf::FieldKey::TypeName.as_str();
dst.set_field(path, field, value);
}
if !exists && spec_type == sdf::SpecType::Attribute && !authored_type_name {
return Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "an attribute spec cannot be authored without a typeName",
});
}
Ok(())
}
fn is_copyable(spec_type: sdf::SpecType) -> bool {
match spec_type {
sdf::SpecType::Prim
| sdf::SpecType::Attribute
| sdf::SpecType::Relationship
| sdf::SpecType::Variant
| sdf::SpecType::VariantSet
| sdf::SpecType::PseudoRoot => true,
sdf::SpecType::Unknown
| sdf::SpecType::Connection
| sdf::SpecType::Expression
| sdf::SpecType::Mapper
| sdf::SpecType::MapperArg
| sdf::SpecType::RelationshipTarget => false,
}
}
fn create_dst_spec(
dst: &mut dyn sdf::AbstractData,
dst_path: &sdf::Path,
spec_type: sdf::SpecType,
) -> Result<(), sdf::AuthoringError> {
match spec_type {
sdf::SpecType::Prim | sdf::SpecType::Variant => sdf::spec::ensure_prim_chain(dst, dst_path)?,
sdf::SpecType::Attribute => {
sdf::AttributeSpecMut::new(dst, dst_path.clone(), "", sdf::Variability::Varying, false)?;
}
sdf::SpecType::Relationship => {
sdf::RelationshipSpecMut::new(dst, dst_path.clone(), sdf::Variability::Varying, false)?;
}
sdf::SpecType::VariantSet => sdf::spec::ensure_variant_set(dst, dst_path)?,
sdf::SpecType::PseudoRoot => {}
sdf::SpecType::Unknown
| sdf::SpecType::Connection
| sdf::SpecType::Expression
| sdf::SpecType::Mapper
| sdf::SpecType::MapperArg
| sdf::SpecType::RelationshipTarget => {
debug_assert!(false, "create_dst_spec called for non-materialized {spec_type}");
}
}
Ok(())
}
fn child_fields(spec_type: sdf::SpecType) -> &'static [sdf::ChildrenKey] {
use sdf::ChildrenKey::{PrimChildren, PropertyChildren, VariantChildren, VariantSetChildren};
match spec_type {
sdf::SpecType::Prim | sdf::SpecType::Variant => &[PrimChildren, PropertyChildren, VariantSetChildren],
sdf::SpecType::VariantSet => &[VariantChildren],
sdf::SpecType::PseudoRoot => &[PrimChildren],
sdf::SpecType::Attribute
| sdf::SpecType::Relationship
| sdf::SpecType::Unknown
| sdf::SpecType::Connection
| sdf::SpecType::Expression
| sdf::SpecType::Mapper
| sdf::SpecType::MapperArg
| sdf::SpecType::RelationshipTarget => &[],
}
}
fn join_child(parent: &sdf::Path, key: sdf::ChildrenKey, name: &str) -> Result<sdf::Path, sdf::AuthoringError> {
let invalid = || sdf::AuthoringError::InvalidPath {
path: parent.clone(),
reason: "child name is not a valid path component",
};
match key {
sdf::ChildrenKey::PrimChildren => parent.append_path(name).map_err(|_| invalid()),
sdf::ChildrenKey::PropertyChildren => parent.append_property(name).map_err(|_| invalid()),
sdf::ChildrenKey::VariantSetChildren => Ok(parent.append_variant_selection(name, "")),
sdf::ChildrenKey::VariantChildren => {
let prim = parent.parent().ok_or_else(invalid)?;
let set = parent.variant_set_name().ok_or_else(invalid)?;
Ok(prim.append_variant_selection(set, name))
}
_ => Err(invalid()),
}
}
fn read_child_names(
src: &dyn sdf::AbstractData,
path: &sdf::Path,
key: sdf::ChildrenKey,
) -> Result<Vec<tf::Token>, sdf::AuthoringError> {
let Some(value) = src.try_field(path, key.as_str())? else {
return Ok(Vec::new());
};
Ok(match value.into_owned() {
sdf::Value::TokenVec(names) => names,
_ => Vec::new(),
})
}
pub(crate) fn is_children_field(field: &str) -> bool {
field == sdf::ChildrenKey::PrimChildren.as_str()
|| field == sdf::ChildrenKey::PropertyChildren.as_str()
|| field == sdf::ChildrenKey::VariantSetChildren.as_str()
|| field == sdf::ChildrenKey::VariantChildren.as_str()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdf::{path, AbstractData, Data, FieldKey, PathListOp, SpecType, Value};
fn sample() -> Data {
let mut data = Data::new();
sdf::PrimSpecMut::new(&mut data, path("/A").unwrap(), sdf::Specifier::Def, "Xform").unwrap();
sdf::PrimSpecMut::new(&mut data, path("/A/Child").unwrap(), sdf::Specifier::Def, "Mesh").unwrap();
sdf::RelationshipSpecMut::new(&mut data, path("/A.rel").unwrap(), sdf::Variability::Varying, false).unwrap();
data.set_field(
&path("/A.rel").unwrap(),
FieldKey::TargetPaths.as_str(),
Value::PathListOp(PathListOp::explicit([
path("/A/Child").unwrap(),
path("/Outside").unwrap(),
])),
);
sdf::AttributeSpecMut::new(
&mut data,
path("/A.attr").unwrap(),
"float",
sdf::Variability::Varying,
false,
)
.unwrap();
data.set_field(
&path("/A.attr").unwrap(),
FieldKey::ConnectionPaths.as_str(),
Value::PathListOp(PathListOp::explicit([path("/A/Child.attr").unwrap()])),
);
data.set_field(
&path("/A").unwrap(),
FieldKey::InheritPaths.as_str(),
Value::PathListOp(PathListOp::explicit([path("/A/Class").unwrap()])),
);
data
}
fn targets(data: &dyn AbstractData, prop: &str, field: FieldKey) -> Vec<String> {
data.try_field(&path(prop).unwrap(), field.as_str())
.unwrap()
.unwrap()
.into_owned()
.try_as_path_list_op()
.unwrap()
.explicit_items
.iter()
.map(|p| p.as_str().to_owned())
.collect()
}
#[test]
fn copy_subtree_reroots() {
let src = sample();
let mut dst = Data::new();
assert!(copy_spec(&src, &path("/A").unwrap(), &mut dst, &path("/B").unwrap()).unwrap());
assert_eq!(dst.spec_type(&path("/B").unwrap()), Some(SpecType::Prim));
assert_eq!(dst.spec_type(&path("/B/Child").unwrap()), Some(SpecType::Prim));
assert_eq!(dst.spec_type(&path("/B.attr").unwrap()), Some(SpecType::Attribute));
assert_eq!(dst.spec_type(&path("/B.rel").unwrap()), Some(SpecType::Relationship));
let children = dst
.try_field(&path("/B").unwrap(), sdf::ChildrenKey::PrimChildren.as_str())
.unwrap()
.unwrap()
.into_owned()
.try_as_token_vec()
.unwrap();
assert!(children.iter().any(|t| t == "Child"));
}
#[test]
fn copy_reroots_paths() {
let src = sample();
let mut dst = Data::new();
copy_spec(&src, &path("/A").unwrap(), &mut dst, &path("/B").unwrap()).unwrap();
assert_eq!(
targets(&dst, "/B.rel", FieldKey::TargetPaths),
vec!["/B/Child", "/Outside"]
);
assert_eq!(
targets(&dst, "/B.attr", FieldKey::ConnectionPaths),
vec!["/B/Child.attr"]
);
assert_eq!(targets(&dst, "/B", FieldKey::InheritPaths), vec!["/B/Class"]);
}
#[test]
fn copy_missing_source_is_noop() {
let src = Data::new();
let mut dst = Data::new();
assert!(!copy_spec(&src, &path("/Nope").unwrap(), &mut dst, &path("/B").unwrap()).unwrap());
assert_eq!(dst.spec_type(&path("/B").unwrap()), None);
}
#[test]
fn copy_replaces_destination() {
let src = sample();
let mut dst = Data::new();
sdf::PrimSpecMut::new(&mut dst, path("/B").unwrap(), sdf::Specifier::Def, "Scope").unwrap();
sdf::PrimSpecMut::new(&mut dst, path("/B/Stale").unwrap(), sdf::Specifier::Def, "Mesh").unwrap();
copy_spec(&src, &path("/A").unwrap(), &mut dst, &path("/B").unwrap()).unwrap();
assert_eq!(dst.spec_type(&path("/B/Stale").unwrap()), None);
assert_eq!(dst.spec_type(&path("/B/Child").unwrap()), Some(SpecType::Prim));
let type_name = dst
.try_field(&path("/B").unwrap(), FieldKey::TypeName.as_str())
.unwrap()
.unwrap()
.into_owned()
.try_as_token()
.unwrap();
assert_eq!(type_name.as_str(), "Xform");
}
#[test]
fn copy_with_prunes() {
let src = sample();
let mut dst = Data::new();
let value_policy = |args: CopyValueArgs| {
if args.field == FieldKey::TargetPaths.as_str() {
CopyValue::Skip
} else {
should_copy_value(args, &path("/A").unwrap(), &path("/B").unwrap())
}
};
let children_policy = |args: CopyChildrenArgs| {
CopyChildren::Map(
args.children
.iter()
.filter(|n| *n != "Child")
.map(|n| (n.clone(), n.clone()))
.collect(),
)
};
copy_spec_with(
&src,
&path("/A").unwrap(),
&mut dst,
&path("/B").unwrap(),
value_policy,
children_policy,
)
.unwrap();
assert_eq!(dst.spec_type(&path("/B/Child").unwrap()), None);
assert!(dst
.try_field(&path("/B.rel").unwrap(), FieldKey::TargetPaths.as_str())
.unwrap()
.is_none());
}
#[test]
fn copy_noncopyable_source_preserves_dst() {
let mut src = Data::new();
src.create_spec(path("/X").unwrap(), SpecType::Connection);
let mut dst = Data::new();
sdf::PrimSpecMut::new(&mut dst, path("/B").unwrap(), sdf::Specifier::Def, "Scope").unwrap();
assert!(!copy_spec(&src, &path("/X").unwrap(), &mut dst, &path("/B").unwrap()).unwrap());
assert_eq!(dst.spec_type(&path("/B").unwrap()), Some(SpecType::Prim));
}
#[test]
fn copy_with_skipped_type_name() {
let src = sample();
let mut dst = Data::new();
let value_policy = |args: CopyValueArgs| {
if args.field == FieldKey::TypeName.as_str() {
CopyValue::Skip
} else {
CopyValue::Copy
}
};
copy_spec_with(
&src,
&path("/A.attr").unwrap(),
&mut dst,
&path("/B.attr").unwrap(),
value_policy,
should_copy_children,
)
.unwrap();
assert_eq!(dst.spec_type(&path("/B.attr").unwrap()), Some(SpecType::Attribute));
assert!(dst
.try_field(&path("/B.attr").unwrap(), FieldKey::TypeName.as_str())
.unwrap()
.is_none());
}
#[test]
fn copy_into_variant() {
let src = sample();
let mut dst = Data::new();
copy_spec(&src, &path("/A").unwrap(), &mut dst, &path("/V{set=sel}Model").unwrap()).unwrap();
assert_eq!(dst.spec_type(&path("/V").unwrap()), Some(SpecType::Prim));
assert_eq!(dst.spec_type(&path("/V{set=}").unwrap()), Some(SpecType::VariantSet));
assert_eq!(dst.spec_type(&path("/V{set=sel}").unwrap()), Some(SpecType::Variant));
assert_eq!(dst.spec_type(&path("/V{set=sel}Model").unwrap()), Some(SpecType::Prim));
assert_eq!(
dst.spec_type(&path("/V{set=sel}Model/Child").unwrap()),
Some(SpecType::Prim)
);
assert_eq!(
targets(&dst, "/V{set=sel}Model.rel", FieldKey::TargetPaths),
vec!["/V{set=sel}Model/Child", "/Outside"]
);
}
#[test]
fn copy_remaps_internal_reference() {
let mut src = Data::new();
sdf::PrimSpecMut::new(&mut src, path("/A").unwrap(), sdf::Specifier::Def, "").unwrap();
src.set_field(
&path("/A").unwrap(),
FieldKey::References.as_str(),
Value::ReferenceListOp(sdf::ReferenceListOp::prepended([
sdf::Reference {
prim_path: path("/A/Inner").unwrap(),
..Default::default()
},
sdf::Reference {
asset_path: "other.usda".into(),
prim_path: path("/A/Ext").unwrap(),
..Default::default()
},
sdf::Reference::default(),
])),
);
let mut dst = Data::new();
copy_spec(&src, &path("/A").unwrap(), &mut dst, &path("/B").unwrap()).unwrap();
let references = dst
.try_field(&path("/B").unwrap(), FieldKey::References.as_str())
.unwrap()
.unwrap()
.into_owned()
.try_as_reference_list_op()
.unwrap();
assert_eq!(references.prepended_items[0].prim_path.as_str(), "/B/Inner");
assert_eq!(references.prepended_items[1].prim_path.as_str(), "/A/Ext");
assert!(references.prepended_items[2].prim_path.is_empty());
}
#[test]
fn copy_prim_onto_variant() {
let src = sample();
let mut dst = Data::new();
copy_spec(&src, &path("/A").unwrap(), &mut dst, &path("/V{set=sel}").unwrap()).unwrap();
assert_eq!(dst.spec_type(&path("/V{set=}").unwrap()), Some(SpecType::VariantSet));
assert_eq!(dst.spec_type(&path("/V{set=sel}").unwrap()), Some(SpecType::Variant));
assert_eq!(dst.spec_type(&path("/V{set=sel}Child").unwrap()), Some(SpecType::Prim));
let type_name = dst
.try_field(&path("/V{set=sel}").unwrap(), FieldKey::TypeName.as_str())
.unwrap()
.unwrap()
.into_owned()
.try_as_token()
.unwrap();
assert_eq!(type_name.as_str(), "Xform");
}
#[test]
fn within_moves_subtree() {
let mut data = sample();
assert!(copy_spec_within(&mut data, &path("/A").unwrap(), &path("/B").unwrap()).unwrap());
assert_eq!(data.spec_type(&path("/B").unwrap()), Some(SpecType::Prim));
assert_eq!(data.spec_type(&path("/B/Child").unwrap()), Some(SpecType::Prim));
assert_eq!(data.spec_type(&path("/B.attr").unwrap()), Some(SpecType::Attribute));
assert_eq!(data.spec_type(&path("/A").unwrap()), Some(SpecType::Prim));
}
#[test]
fn within_reroots_paths() {
let mut data = sample();
copy_spec_within(&mut data, &path("/A").unwrap(), &path("/B").unwrap()).unwrap();
assert_eq!(
targets(&data, "/B.rel", FieldKey::TargetPaths),
vec!["/B/Child", "/Outside"]
);
assert_eq!(
targets(&data, "/B.attr", FieldKey::ConnectionPaths),
vec!["/B/Child.attr"]
);
assert_eq!(targets(&data, "/B", FieldKey::InheritPaths), vec!["/B/Class"]);
}
#[test]
fn within_missing_source_noop() {
let mut data = Data::new();
assert!(!copy_spec_within(&mut data, &path("/Nope").unwrap(), &path("/B").unwrap()).unwrap());
assert_eq!(data.spec_type(&path("/B").unwrap()), None);
}
#[test]
fn copy_source_variant() {
let mut src = Data::new();
sdf::PrimSpecMut::new(&mut src, path("/A").unwrap(), sdf::Specifier::Def, "Xform").unwrap();
sdf::PrimSpecMut::over(&mut src, path("/A{look=red}Inside").unwrap()).unwrap();
let mut dst = Data::new();
copy_spec(&src, &path("/A").unwrap(), &mut dst, &path("/B").unwrap()).unwrap();
assert_eq!(dst.spec_type(&path("/B{look=}").unwrap()), Some(SpecType::VariantSet));
assert_eq!(dst.spec_type(&path("/B{look=red}").unwrap()), Some(SpecType::Variant));
assert_eq!(
dst.spec_type(&path("/B{look=red}Inside").unwrap()),
Some(SpecType::Prim)
);
}
}