use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use strum::{Display, EnumCount, FromRepr};
use crate::sdf;
use crate::tf;
#[repr(u32)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, FromRepr, EnumCount, Display)]
pub enum SpecType {
#[default]
Unknown = 0,
Attribute = 1,
Connection = 2,
Expression = 3,
Mapper = 4,
MapperArg = 5,
Prim = 6,
PseudoRoot = 7,
Relationship = 8,
RelationshipTarget = 9,
Variant = 10,
VariantSet = 11,
}
macro_rules! impl_spec_debug {
($($ty:ident),+ $(,)?) => {$(
impl<'a, B: Deref<Target = dyn sdf::AbstractData + 'a>> fmt::Debug for $ty<'a, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
)+};
}
#[derive(Debug, Clone)]
pub struct SpecData {
pub ty: sdf::SpecType,
pub fields: Vec<(String, sdf::Value)>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum SpecError {
#[error("field {field} exists with non-{expected} value")]
FieldType {
field: &'static str,
expected: &'static str,
},
}
impl SpecData {
pub fn new(ty: sdf::SpecType) -> Self {
Self { ty, fields: Vec::new() }
}
pub fn add(&mut self, key: impl AsRef<str>, value: impl Into<sdf::Value>) {
let key = key.as_ref();
let value = value.into();
if let Some(slot) = self.fields.iter_mut().find(|(k, _)| k == key) {
slot.1 = value;
} else {
self.fields.push((key.to_owned(), value));
}
}
pub fn add_list_op(&mut self, key: impl AsRef<str>, value: sdf::Value) {
let key = key.as_ref();
let Some(slot) = self.get_mut(key) else {
self.add(key, value);
return;
};
use sdf::Value::*;
match (slot, value) {
(TokenListOp(existing), TokenListOp(incoming)) => existing.merge_op(incoming),
(StringListOp(existing), StringListOp(incoming)) => existing.merge_op(incoming),
(PathListOp(existing), PathListOp(incoming)) => existing.merge_op(incoming),
(ReferenceListOp(existing), ReferenceListOp(incoming)) => existing.merge_op(incoming),
(PayloadListOp(existing), PayloadListOp(incoming)) => existing.merge_op(incoming),
(IntListOp(existing), IntListOp(incoming)) => existing.merge_op(incoming),
(Int64ListOp(existing), Int64ListOp(incoming)) => existing.merge_op(incoming),
(UIntListOp(existing), UIntListOp(incoming)) => existing.merge_op(incoming),
(UInt64ListOp(existing), UInt64ListOp(incoming)) => existing.merge_op(incoming),
(UnregisteredValueListOp(existing), UnregisteredValueListOp(incoming)) => existing.merge_op(incoming),
(slot, value) => *slot = value,
}
}
pub fn get(&self, key: &str) -> Option<&sdf::Value> {
self.fields.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn get_mut(&mut self, key: &str) -> Option<&mut sdf::Value> {
self.fields.iter_mut().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn contains(&self, key: &str) -> bool {
self.fields.iter().any(|(k, _)| k == key)
}
pub fn remove(&mut self, key: &str) -> Option<sdf::Value> {
let idx = self.fields.iter().position(|(k, _)| k == key)?;
Some(self.fields.remove(idx).1)
}
pub fn extend_from(&mut self, other: SpecData) {
for (k, v) in other.fields {
self.add(k, v);
}
}
}
pub struct Spec<'a, B> {
data: B,
path: sdf::Path,
_marker: PhantomData<&'a ()>,
}
impl<'a, B: Deref<Target = dyn sdf::AbstractData + 'a>> fmt::Debug for Spec<'a, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Spec")
.field("path", &self.path)
.field("type", &self.spec_type())
.finish()
}
}
impl_spec_debug!(PrimSpec, PseudoRootSpec, PropertySpec, AttributeSpec, RelationshipSpec);
pub type SpecRef<'a> = Spec<'a, &'a dyn sdf::AbstractData>;
pub type SpecMut<'a> = Spec<'a, &'a mut dyn sdf::AbstractData>;
impl<'a, B> Spec<'a, B> {
pub(crate) fn wrap(data: B, path: sdf::Path) -> Self {
Self {
data,
path,
_marker: PhantomData,
}
}
}
impl<'a, B> Spec<'a, B>
where
B: Deref<Target = dyn sdf::AbstractData + 'a>,
{
pub fn path(&self) -> &sdf::Path {
&self.path
}
pub fn spec_type(&self) -> Option<sdf::SpecType> {
self.data.spec_type(&self.path)
}
pub fn field(&self, key: &str) -> anyhow::Result<Option<sdf::Value>> {
Ok(self.data.try_field(&self.path, key)?.map(|c| c.into_owned()))
}
pub fn get<T: TryFrom<sdf::Value>>(&self, key: impl AsRef<str>) -> Option<T> {
self.field(key.as_ref()).ok().flatten()?.get()
}
pub fn has_field(&self, key: &str) -> bool {
self.data.has_field(&self.path, key)
}
pub fn fields(&self) -> Vec<String> {
self.data.list_fields(&self.path).unwrap_or_default()
}
}
impl<'a, B> Spec<'a, B>
where
B: DerefMut<Target = dyn sdf::AbstractData + 'a>,
{
pub fn set(&mut self, key: impl AsRef<str>, value: impl Into<sdf::Value>) {
self.data.set_field(&self.path, key.as_ref(), value.into());
}
pub fn erase(&mut self, key: &str) {
self.data.erase_field(&self.path, key);
}
}
#[derive(derive_more::Deref, derive_more::DerefMut)]
pub struct PrimSpec<'a, B>(Spec<'a, B>);
pub type PrimSpecRef<'a> = PrimSpec<'a, &'a dyn sdf::AbstractData>;
pub type PrimSpecMut<'a> = PrimSpec<'a, &'a mut dyn sdf::AbstractData>;
impl<'a> PrimSpecRef<'a> {
pub(crate) fn get(data: &'a dyn sdf::AbstractData, path: sdf::Path) -> Option<Self> {
matches!(data.spec_type(&path), Some(sdf::SpecType::Prim)).then(|| Self(Spec::wrap(data, path)))
}
}
impl<'a> PrimSpecMut<'a> {
pub(crate) fn get(data: &'a mut dyn sdf::AbstractData, path: sdf::Path) -> Option<Self> {
matches!(data.spec_type(&path), Some(sdf::SpecType::Prim)).then(|| Self(Spec::wrap(data, path)))
}
pub fn new(
data: &'a mut dyn sdf::AbstractData,
path: impl Into<sdf::Path>,
specifier: sdf::Specifier,
type_name: impl Into<String>,
) -> Result<Self, sdf::AuthoringError> {
let path = path.into();
let type_name: String = type_name.into();
require_prim_leaf(&path)?;
ensure_prim_chain(data, &path)?;
data.set_field(
&path,
sdf::FieldKey::Specifier.as_str(),
sdf::Value::Specifier(specifier),
);
if !type_name.is_empty() {
data.set_field(&path, sdf::FieldKey::TypeName.as_str(), sdf::Value::token(type_name));
}
Ok(Self::get(data, path).expect("ensure_prim_chain created a prim spec"))
}
pub fn over(data: &'a mut dyn sdf::AbstractData, path: impl Into<sdf::Path>) -> Result<Self, sdf::AuthoringError> {
let path = path.into();
require_prim_leaf(&path)?;
ensure_prim_chain(data, &path)?;
Ok(Self::get(data, path).expect("ensure_prim_chain created a prim spec"))
}
}
impl<'a, B> PrimSpec<'a, B>
where
B: Deref<Target = dyn sdf::AbstractData + 'a>,
{
pub fn type_name(&self) -> Option<tf::Token> {
self.get(sdf::FieldKey::TypeName)
}
pub fn specifier(&self) -> Option<sdf::Specifier> {
self.get(sdf::FieldKey::Specifier)
}
pub fn kind(&self) -> Option<tf::Token> {
self.get(sdf::FieldKey::Kind)
}
pub fn is_active(&self) -> Option<bool> {
self.get(sdf::FieldKey::Active)
}
pub fn is_hidden(&self) -> Option<bool> {
self.get(sdf::FieldKey::Hidden)
}
pub fn is_instanceable(&self) -> Option<bool> {
self.get(sdf::FieldKey::Instanceable)
}
pub fn prim_children(&self) -> Option<Vec<tf::Token>> {
self.get(sdf::ChildrenKey::PrimChildren)
}
pub fn property_children(&self) -> Option<Vec<tf::Token>> {
self.get(sdf::ChildrenKey::PropertyChildren)
}
pub fn api_schemas(&self) -> Option<sdf::TokenListOp> {
self.get(sdf::FieldKey::ApiSchemas)
}
}
impl<'a, B> PrimSpec<'a, B>
where
B: DerefMut<Target = dyn sdf::AbstractData + 'a>,
{
pub fn set_type_name(&mut self, name: impl Into<tf::Token>) {
let name = name.into();
if name.is_empty() {
self.erase(sdf::FieldKey::TypeName.as_str());
} else {
self.set(sdf::FieldKey::TypeName.as_str(), sdf::Value::Token(name));
}
}
pub fn set_specifier(&mut self, specifier: sdf::Specifier) {
self.set(sdf::FieldKey::Specifier.as_str(), sdf::Value::Specifier(specifier));
}
pub fn set_kind(&mut self, kind: impl Into<tf::Token>) {
self.set(sdf::FieldKey::Kind.as_str(), sdf::Value::token(kind));
}
pub fn set_active(&mut self, active: bool) {
self.set(sdf::FieldKey::Active.as_str(), sdf::Value::Bool(active));
}
pub fn set_hidden(&mut self, hidden: bool) {
self.set(sdf::FieldKey::Hidden.as_str(), sdf::Value::Bool(hidden));
}
pub fn set_instanceable(&mut self, instanceable: bool) {
self.set(sdf::FieldKey::Instanceable.as_str(), sdf::Value::Bool(instanceable));
}
pub fn add_applied_schema(&mut self, name: impl Into<String>) -> Result<bool, SpecError> {
let name: tf::Token = name.into().into();
let Ok(existing) = self.field(sdf::FieldKey::ApiSchemas.as_str()) else {
return Ok(false);
};
match existing {
Some(sdf::Value::TokenListOp(mut op)) => {
let changed = add_applied_schema_to_list_op(&mut op, name);
self.set(sdf::FieldKey::ApiSchemas.as_str(), sdf::Value::TokenListOp(op));
Ok(changed)
}
Some(_) => Err(SpecError::FieldType {
field: sdf::FieldKey::ApiSchemas.as_str(),
expected: "sdf::TokenListOp",
}),
None => {
self.set(
sdf::FieldKey::ApiSchemas.as_str(),
sdf::Value::TokenListOp(sdf::TokenListOp::prepended([name])),
);
Ok(true)
}
}
}
}
fn add_applied_schema_to_list_op(op: &mut sdf::TokenListOp, name: tf::Token) -> bool {
let already_applied = op.explicit_items.iter().any(|n| n == &name)
|| op.prepended_items.iter().any(|n| n == &name)
|| op.appended_items.iter().any(|n| n == &name)
|| (!op.explicit && op.added_items.iter().any(|n| n == &name));
let before = op.deleted_items.len();
op.deleted_items.retain(|n| n != &name);
let mut changed = op.deleted_items.len() != before;
if already_applied {
return changed;
}
if op.explicit {
op.explicit_items.push(name);
} else {
op.prepended_items.push(name);
}
changed = true;
changed
}
#[derive(derive_more::Deref, derive_more::DerefMut)]
pub struct PseudoRootSpec<'a, B>(Spec<'a, B>);
pub type PseudoRootSpecRef<'a> = PseudoRootSpec<'a, &'a dyn sdf::AbstractData>;
pub type PseudoRootSpecMut<'a> = PseudoRootSpec<'a, &'a mut dyn sdf::AbstractData>;
impl<'a> PseudoRootSpecRef<'a> {
pub(crate) fn get(data: &'a dyn sdf::AbstractData) -> Option<Self> {
let path = sdf::Path::abs_root();
matches!(data.spec_type(&path), Some(sdf::SpecType::PseudoRoot)).then(|| Self(Spec::wrap(data, path)))
}
}
impl<'a> PseudoRootSpecMut<'a> {
pub(crate) fn get(data: &'a mut dyn sdf::AbstractData) -> Option<Self> {
let path = sdf::Path::abs_root();
matches!(data.spec_type(&path), Some(sdf::SpecType::PseudoRoot)).then(|| Self(Spec::wrap(data, path)))
}
}
impl<'a, B> PseudoRootSpec<'a, B>
where
B: Deref<Target = dyn sdf::AbstractData + 'a>,
{
pub fn default_prim(&self) -> Option<tf::Token> {
self.get(sdf::FieldKey::DefaultPrim)
}
pub fn sublayers(&self) -> Option<Vec<String>> {
self.get(sdf::FieldKey::SubLayers)
}
pub fn relocates(&self) -> Option<sdf::RelocateList> {
self.get(sdf::FieldKey::LayerRelocates)
}
pub fn documentation(&self) -> Option<String> {
self.get(sdf::FieldKey::Documentation)
}
pub fn start_time_code(&self) -> Option<f64> {
self.get(sdf::FieldKey::StartTimeCode)
}
pub fn end_time_code(&self) -> Option<f64> {
self.get(sdf::FieldKey::EndTimeCode)
}
pub fn time_codes_per_second(&self) -> Option<f64> {
self.get(sdf::FieldKey::TimeCodesPerSecond)
}
pub fn frames_per_second(&self) -> Option<f64> {
self.get(sdf::FieldKey::FramesPerSecond)
}
pub fn frame_precision(&self) -> Option<i32> {
self.get(sdf::FieldKey::FramePrecision)
}
pub fn prim_children(&self) -> Option<Vec<tf::Token>> {
self.get(sdf::ChildrenKey::PrimChildren)
}
}
impl<'a, B> PseudoRootSpec<'a, B>
where
B: DerefMut<Target = dyn sdf::AbstractData + 'a>,
{
pub fn set_default_prim(&mut self, name: impl Into<tf::Token>) {
self.set(sdf::FieldKey::DefaultPrim.as_str(), sdf::Value::token(name));
}
pub fn set_sublayers<I, S>(&mut self, paths: I)
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let paths: Vec<String> = paths.into_iter().map(Into::into).collect();
self.set(sdf::FieldKey::SubLayers.as_str(), sdf::Value::StringVec(paths));
}
pub fn set_relocates(&mut self, relocates: sdf::RelocateList) {
self.set(sdf::FieldKey::LayerRelocates.as_str(), sdf::Value::Relocates(relocates));
}
pub fn set_expression_variables(&mut self, vars: HashMap<String, sdf::Value>) {
self.set(
sdf::FieldKey::ExpressionVariables.as_str(),
sdf::Value::Dictionary(vars),
);
}
pub fn add_sublayer(&mut self, path: impl Into<String>) {
let path = path.into();
let mut paths = self.sublayer_paths().unwrap_or_default();
paths.push(path);
self.set(sdf::FieldKey::SubLayers.as_str(), sdf::Value::StringVec(paths));
}
pub fn insert_sublayer(&mut self, pos: usize, path: impl Into<String>, offset: sdf::LayerOffset) {
let mut paths = self.sublayer_paths().unwrap_or_default();
let mut offsets = self.sublayer_offsets(paths.len());
let pos = pos.min(paths.len());
paths.insert(pos, path.into());
offsets.insert(pos, offset);
self.set(sdf::FieldKey::SubLayers.as_str(), sdf::Value::StringVec(paths));
self.set(
sdf::FieldKey::SubLayerOffsets.as_str(),
sdf::Value::LayerOffsetVec(offsets),
);
}
pub fn remove_sublayer(&mut self, path: &str) -> bool {
let Some(mut paths) = self.sublayer_paths() else {
return false;
};
let Some(idx) = paths.iter().position(|p| p == path) else {
return false;
};
let mut offsets = self.sublayer_offsets(paths.len());
paths.remove(idx);
offsets.remove(idx);
self.set(sdf::FieldKey::SubLayers.as_str(), sdf::Value::StringVec(paths));
self.set(
sdf::FieldKey::SubLayerOffsets.as_str(),
sdf::Value::LayerOffsetVec(offsets),
);
true
}
fn sublayer_paths(&self) -> Option<Vec<String>> {
self.get(sdf::FieldKey::SubLayers)
}
fn sublayer_offsets(&self, len: usize) -> Vec<sdf::LayerOffset> {
let mut offsets = self
.get::<Vec<sdf::LayerOffset>>(sdf::FieldKey::SubLayerOffsets)
.unwrap_or_default();
offsets.resize(len, sdf::LayerOffset::IDENTITY);
offsets
}
pub fn set_documentation(&mut self, doc: impl Into<String>) {
self.set(sdf::FieldKey::Documentation.as_str(), sdf::Value::String(doc.into()));
}
pub fn set_start_time_code(&mut self, time: f64) {
self.set(sdf::FieldKey::StartTimeCode.as_str(), sdf::Value::Double(time));
}
pub fn set_end_time_code(&mut self, time: f64) {
self.set(sdf::FieldKey::EndTimeCode.as_str(), sdf::Value::Double(time));
}
pub fn set_time_codes_per_second(&mut self, rate: f64) {
self.set(sdf::FieldKey::TimeCodesPerSecond.as_str(), sdf::Value::Double(rate));
}
pub fn set_frames_per_second(&mut self, rate: f64) {
self.set(sdf::FieldKey::FramesPerSecond.as_str(), sdf::Value::Double(rate));
}
pub fn set_frame_precision(&mut self, precision: i32) {
self.set(sdf::FieldKey::FramePrecision.as_str(), sdf::Value::Int(precision));
}
}
#[derive(derive_more::Deref, derive_more::DerefMut)]
pub struct PropertySpec<'a, B>(Spec<'a, B>);
pub type PropertySpecRef<'a> = PropertySpec<'a, &'a dyn sdf::AbstractData>;
pub type PropertySpecMut<'a> = PropertySpec<'a, &'a mut dyn sdf::AbstractData>;
impl<'a, B> PropertySpec<'a, B>
where
B: Deref<Target = dyn sdf::AbstractData + 'a>,
{
pub fn variability(&self) -> sdf::Variability {
self.get(sdf::FieldKey::Variability)
.unwrap_or(sdf::Variability::Varying)
}
pub fn is_custom(&self) -> bool {
self.get(sdf::FieldKey::Custom).unwrap_or(false)
}
}
impl<'a, B> PropertySpec<'a, B>
where
B: DerefMut<Target = dyn sdf::AbstractData + 'a>,
{
pub fn set_custom(&mut self, custom: bool) {
self.set(sdf::FieldKey::Custom.as_str(), sdf::Value::Bool(custom));
}
}
#[derive(derive_more::Deref, derive_more::DerefMut)]
pub struct AttributeSpec<'a, B>(PropertySpec<'a, B>);
pub type AttributeSpecRef<'a> = AttributeSpec<'a, &'a dyn sdf::AbstractData>;
pub type AttributeSpecMut<'a> = AttributeSpec<'a, &'a mut dyn sdf::AbstractData>;
impl<'a> AttributeSpecRef<'a> {
pub(crate) fn get(data: &'a dyn sdf::AbstractData, path: sdf::Path) -> Option<Self> {
matches!(data.spec_type(&path), Some(sdf::SpecType::Attribute))
.then(|| Self(PropertySpec(Spec::wrap(data, path))))
}
}
impl<'a> AttributeSpecMut<'a> {
pub(crate) fn get(data: &'a mut dyn sdf::AbstractData, path: sdf::Path) -> Option<Self> {
matches!(data.spec_type(&path), Some(sdf::SpecType::Attribute))
.then(|| Self(PropertySpec(Spec::wrap(data, path))))
}
pub fn new(
data: &'a mut dyn sdf::AbstractData,
path: impl Into<sdf::Path>,
type_name: impl Into<String>,
variability: sdf::Variability,
custom: bool,
) -> Result<Self, sdf::AuthoringError> {
let path = path.into();
let type_name = Some(type_name.into());
create_property_spec(data, &path, sdf::SpecType::Attribute, type_name, variability, custom)?;
Ok(Self::get(data, path).expect("type guaranteed by require_spec_type_or_absent"))
}
}
impl<'a, B> AttributeSpec<'a, B>
where
B: Deref<Target = dyn sdf::AbstractData + 'a>,
{
pub fn type_name(&self) -> Option<tf::Token> {
self.get(sdf::FieldKey::TypeName)
}
pub fn default(&self) -> Option<sdf::Value> {
self.get(sdf::FieldKey::Default)
}
pub fn time_samples(&self) -> Option<Vec<(f64, sdf::Value)>> {
self.get(sdf::FieldKey::TimeSamples)
}
pub fn color_space(&self) -> Option<tf::Token> {
self.get(sdf::FieldKey::ColorSpace)
}
pub fn allowed_tokens(&self) -> Option<Vec<tf::Token>> {
self.get(sdf::FieldKey::AllowedTokens)
}
pub fn connection_path_list(&self) -> Option<sdf::PathListOp> {
self.get(sdf::FieldKey::ConnectionPaths)
}
}
impl<'a, B> AttributeSpec<'a, B>
where
B: DerefMut<Target = dyn sdf::AbstractData + 'a>,
{
pub fn set_default(&mut self, value: impl Into<sdf::Value>) {
self.set(sdf::FieldKey::Default.as_str(), value.into());
}
pub fn clear_default(&mut self) {
self.erase(sdf::FieldKey::Default.as_str());
}
pub fn set_time_sample(&mut self, time: f64, value: impl Into<sdf::Value>) {
let value = value.into();
let Ok(existing) = self.field(sdf::FieldKey::TimeSamples.as_str()) else {
return;
};
let mut map = match existing {
Some(sdf::Value::TimeSamples(map)) => map,
None => Vec::new(),
Some(other) => {
debug_assert!(false, "timeSamples field is not a TimeSamples (got {other:?})");
Vec::new()
}
};
upsert_time_sample(&mut map, time, value);
self.set(sdf::FieldKey::TimeSamples.as_str(), sdf::Value::TimeSamples(map));
}
pub fn erase_time_sample(&mut self, time: f64) -> bool {
let Some(mut map) = self.time_samples() else {
return false;
};
let Some(idx) = map.iter().position(|(t, _)| t.total_cmp(&time).is_eq()) else {
return false;
};
map.remove(idx);
if map.is_empty() {
self.erase(sdf::FieldKey::TimeSamples.as_str());
} else {
self.set(sdf::FieldKey::TimeSamples.as_str(), sdf::Value::TimeSamples(map));
}
true
}
pub fn set_color_space(&mut self, color_space: impl Into<tf::Token>) {
self.set(sdf::FieldKey::ColorSpace.as_str(), sdf::Value::token(color_space));
}
pub fn set_allowed_tokens<I, S>(&mut self, tokens: I)
where
I: IntoIterator<Item = S>,
S: Into<tf::Token>,
{
let tokens: Vec<tf::Token> = tokens.into_iter().map(Into::into).collect();
self.set(sdf::FieldKey::AllowedTokens.as_str(), sdf::Value::TokenVec(tokens));
}
pub fn set_connection_paths<I>(&mut self, paths: I)
where
I: IntoIterator<Item = sdf::Path>,
{
let paths: Vec<sdf::Path> = paths.into_iter().collect();
self.set(
sdf::FieldKey::ConnectionPaths.as_str(),
sdf::Value::PathListOp(sdf::PathListOp::explicit(paths)),
);
}
pub fn add_connection_path(&mut self, path: sdf::Path, prepend: bool) -> bool {
let key = sdf::FieldKey::ConnectionPaths.as_str();
let Ok(existing) = self.field(key) else {
return false;
};
match existing {
Some(sdf::Value::PathListOp(mut op)) => {
let mut changed = remove_path(&mut op.deleted_items, &path);
if op.iter().any(|p| p == &path) {
if changed {
self.set(key, sdf::Value::PathListOp(op));
}
return changed;
}
if op.explicit {
if prepend {
op.explicit_items.insert(0, path);
} else {
op.explicit_items.push(path);
}
} else if prepend {
op.prepended_items.push(path);
} else {
op.appended_items.push(path);
}
changed = true;
self.set(key, sdf::Value::PathListOp(op));
changed
}
Some(other) => {
debug_assert!(false, "connectionPaths field is not a sdf::PathListOp (got {other:?})");
let op = if prepend {
sdf::PathListOp::prepended([path])
} else {
sdf::PathListOp::appended([path])
};
self.set(key, sdf::Value::PathListOp(op));
true
}
None => {
let op = if prepend {
sdf::PathListOp::prepended([path])
} else {
sdf::PathListOp::appended([path])
};
self.set(key, sdf::Value::PathListOp(op));
true
}
}
}
pub fn remove_connection_path(&mut self, path: &sdf::Path) -> bool {
let key = sdf::FieldKey::ConnectionPaths.as_str();
let Some(mut op) = self.get::<sdf::PathListOp>(key) else {
return false;
};
let removed = remove_path(&mut op.explicit_items, path)
| remove_path(&mut op.added_items, path)
| remove_path(&mut op.prepended_items, path)
| remove_path(&mut op.appended_items, path);
self.set(key, sdf::Value::PathListOp(op));
removed
}
pub fn delete_connection_path(&mut self, path: &sdf::Path) -> bool {
let key = sdf::FieldKey::ConnectionPaths.as_str();
let Ok(existing) = self.field(key) else {
return false;
};
match existing {
Some(sdf::Value::PathListOp(mut op)) => {
let removed = remove_path(&mut op.explicit_items, path)
| remove_path(&mut op.added_items, path)
| remove_path(&mut op.prepended_items, path)
| remove_path(&mut op.appended_items, path);
if op.explicit || op.deleted_items.iter().any(|p| p == path) {
self.set(key, sdf::Value::PathListOp(op));
return removed;
}
op.deleted_items.push(path.clone());
self.set(key, sdf::Value::PathListOp(op));
true
}
Some(other) => {
debug_assert!(false, "connectionPaths field is not a sdf::PathListOp (got {other:?})");
self.set(key, sdf::Value::PathListOp(sdf::PathListOp::deleted([path.clone()])));
true
}
None => {
self.set(key, sdf::Value::PathListOp(sdf::PathListOp::deleted([path.clone()])));
true
}
}
}
pub fn clear_connection_paths(&mut self) -> bool {
let key = sdf::FieldKey::ConnectionPaths.as_str();
let present = self.has_field(key);
if present {
self.erase(key);
}
present
}
}
fn upsert_time_sample(map: &mut Vec<(f64, sdf::Value)>, time: f64, value: sdf::Value) {
match map.binary_search_by(|(t, _)| t.total_cmp(&time)) {
Ok(idx) => map[idx].1 = value,
Err(idx) => map.insert(idx, (time, value)),
}
}
#[derive(derive_more::Deref, derive_more::DerefMut)]
pub struct RelationshipSpec<'a, B>(PropertySpec<'a, B>);
pub type RelationshipSpecRef<'a> = RelationshipSpec<'a, &'a dyn sdf::AbstractData>;
pub type RelationshipSpecMut<'a> = RelationshipSpec<'a, &'a mut dyn sdf::AbstractData>;
impl<'a> RelationshipSpecRef<'a> {
pub(crate) fn get(data: &'a dyn sdf::AbstractData, path: sdf::Path) -> Option<Self> {
matches!(data.spec_type(&path), Some(sdf::SpecType::Relationship))
.then(|| Self(PropertySpec(Spec::wrap(data, path))))
}
}
impl<'a> RelationshipSpecMut<'a> {
pub(crate) fn get(data: &'a mut dyn sdf::AbstractData, path: sdf::Path) -> Option<Self> {
matches!(data.spec_type(&path), Some(sdf::SpecType::Relationship))
.then(|| Self(PropertySpec(Spec::wrap(data, path))))
}
pub fn new(
data: &'a mut dyn sdf::AbstractData,
path: impl Into<sdf::Path>,
variability: sdf::Variability,
custom: bool,
) -> Result<Self, sdf::AuthoringError> {
let path = path.into();
create_property_spec(data, &path, sdf::SpecType::Relationship, None, variability, custom)?;
Ok(Self::get(data, path).expect("type guaranteed by require_spec_type_or_absent"))
}
}
impl<'a, B> RelationshipSpec<'a, B>
where
B: Deref<Target = dyn sdf::AbstractData + 'a>,
{
pub fn target_path_list(&self) -> Option<sdf::PathListOp> {
self.get(sdf::FieldKey::TargetPaths)
}
}
impl<'a, B> RelationshipSpec<'a, B>
where
B: DerefMut<Target = dyn sdf::AbstractData + 'a>,
{
pub fn set_target_paths<I>(&mut self, paths: I)
where
I: IntoIterator<Item = sdf::Path>,
{
let paths: Vec<sdf::Path> = paths.into_iter().collect();
self.set(
sdf::FieldKey::TargetPaths.as_str(),
sdf::Value::PathListOp(sdf::PathListOp::explicit(paths)),
);
}
pub fn add_target(&mut self, path: sdf::Path) {
let key = sdf::FieldKey::TargetPaths.as_str();
let Ok(existing) = self.field(key) else {
return;
};
match existing {
Some(sdf::Value::PathListOp(mut op)) => {
if !op.iter().any(|p| p == &path) {
if op.explicit {
op.explicit_items.push(path);
} else {
op.added_items.push(path);
}
}
self.set(key, sdf::Value::PathListOp(op));
}
Some(other) => {
debug_assert!(false, "targetPaths field is not a sdf::PathListOp (got {other:?})");
self.set(key, sdf::Value::PathListOp(sdf::PathListOp::explicit([path])));
}
None => {
self.set(key, sdf::Value::PathListOp(sdf::PathListOp::explicit([path])));
}
}
}
pub fn remove_target(&mut self, path: &sdf::Path) -> bool {
let key = sdf::FieldKey::TargetPaths.as_str();
let Ok(existing) = self.field(key) else {
return false;
};
match existing {
Some(sdf::Value::PathListOp(mut op)) => {
let changed = op.remove(path);
self.set(key, sdf::Value::PathListOp(op));
changed
}
Some(_) => false,
None => {
self.set(key, sdf::Value::PathListOp(sdf::PathListOp::deleted([path.clone()])));
true
}
}
}
}
fn remove_path(paths: &mut Vec<sdf::Path>, path: &sdf::Path) -> bool {
let Some(idx) = paths.iter().position(|p| p == path) else {
return false;
};
paths.remove(idx);
true
}
fn create_property_spec(
data: &mut dyn sdf::AbstractData,
path: &sdf::Path,
spec_type: sdf::SpecType,
type_name: Option<String>,
variability: sdf::Variability,
custom: bool,
) -> Result<(), sdf::AuthoringError> {
let (prim_path, property_name) = split_property_path(path)?;
require_spec_type_or_absent(data, path, spec_type)?;
validate_token_vec(data, &prim_path, sdf::ChildrenKey::PropertyChildren)?;
ensure_prim_chain(data, &prim_path)?;
add_to_token_vec(data, &prim_path, sdf::ChildrenKey::PropertyChildren, &property_name)?;
if !data.has_spec(path) {
data.create_spec(path.clone(), spec_type);
}
if let Some(type_name) = type_name {
data.set_field(path, sdf::FieldKey::TypeName.as_str(), sdf::Value::token(type_name));
}
let varying = variability != sdf::Variability::Varying;
set_or_erase(
data,
path,
sdf::FieldKey::Variability.as_str(),
varying.then_some(sdf::Value::Variability(variability)),
);
set_or_erase(
data,
path,
sdf::FieldKey::Custom.as_str(),
custom.then_some(sdf::Value::Bool(true)),
);
Ok(())
}
fn set_or_erase(data: &mut dyn sdf::AbstractData, path: &sdf::Path, key: &str, value: Option<sdf::Value>) {
match value {
Some(value) => data.set_field(path, key, value),
None => data.erase_field(path, key),
}
}
pub(crate) fn ensure_prim_chain(
data: &mut dyn sdf::AbstractData,
target: &sdf::Path,
) -> Result<(), sdf::AuthoringError> {
let chain = namespace_chain(target)?;
let abs_root = sdf::Path::abs_root();
let root_type = data.spec_type(&abs_root);
if matches!(root_type, Some(ty) if ty != sdf::SpecType::PseudoRoot) {
return Err(sdf::AuthoringError::InvalidPath {
path: abs_root,
reason: "root spec exists with a non-PseudoRoot SpecType",
});
}
let parent_of = |i: usize| if i == 0 { &abs_root } else { &chain[i - 1].path };
for (i, elem) in chain.iter().enumerate() {
if let Some(existing) = data.spec_type(&elem.path) {
if existing != elem.spec_type {
return Err(sdf::AuthoringError::InvalidPath {
path: elem.path.clone(),
reason: "spec exists with an incompatible SpecType",
});
}
}
validate_token_vec(data, parent_of(i), elem.child_key)?;
}
if root_type.is_none() {
data.create_spec(abs_root.clone(), sdf::SpecType::PseudoRoot);
}
for (i, elem) in chain.iter().enumerate() {
add_to_token_vec(data, parent_of(i), elem.child_key, &elem.child_name)?;
if data.spec_type(&elem.path).is_none() {
data.create_spec(elem.path.clone(), elem.spec_type);
if elem.spec_type == sdf::SpecType::Prim {
data.set_field(
&elem.path,
sdf::FieldKey::Specifier.as_str(),
sdf::Value::Specifier(sdf::Specifier::Over),
);
}
}
}
Ok(())
}
pub(crate) fn ensure_variant_set(
data: &mut dyn sdf::AbstractData,
vset_path: &sdf::Path,
) -> Result<(), sdf::AuthoringError> {
let invalid = |reason: &'static str| sdf::AuthoringError::InvalidPath {
path: vset_path.clone(),
reason,
};
let prim = vset_path
.parent()
.ok_or_else(|| invalid("variant-set path has no owning prim"))?;
let set = vset_path
.variant_set_name()
.ok_or_else(|| invalid("path is not a variant-set path"))?;
ensure_prim_chain(data, &prim)?;
add_to_token_vec(data, &prim, sdf::ChildrenKey::VariantSetChildren, set)?;
if data.spec_type(vset_path).is_none() {
data.create_spec(vset_path.clone(), sdf::SpecType::VariantSet);
}
Ok(())
}
struct ChainElement {
path: sdf::Path,
spec_type: sdf::SpecType,
child_key: sdf::ChildrenKey,
child_name: String,
}
fn parse_prim_path(
target: &sdf::Path,
mut emit: impl FnMut(sdf::PathComponent<'_>),
) -> Result<(), sdf::AuthoringError> {
let invalid = |reason: &'static str| sdf::AuthoringError::InvalidPath {
path: target.clone(),
reason,
};
if !target.is_abs() || target.is_abs_root() {
return Err(invalid("expected absolute non-root prim path"));
}
if target.is_property_path() {
return Err(invalid("expected prim path, got property path"));
}
let mut components = target.components();
for component in components.by_ref() {
match component {
sdf::PathComponent::Prim(name) => {
if !sdf::Path::is_valid_identifier(name) {
return Err(invalid("prim path component is not a USD identifier"));
}
}
sdf::PathComponent::Variant { set, selection } => {
if !sdf::Path::is_valid_identifier(set) {
return Err(invalid("variant set name is not a USD identifier"));
}
if selection.is_empty() || !sdf::Path::is_valid_identifier(selection) {
return Err(invalid("variant selection is not a USD identifier"));
}
}
}
emit(component);
}
if !components.remainder().is_empty() {
return Err(invalid("malformed prim path"));
}
Ok(())
}
fn namespace_chain(target: &sdf::Path) -> Result<Vec<ChainElement>, sdf::AuthoringError> {
let mut elems = Vec::new();
let mut cursor = sdf::Path::abs_root();
parse_prim_path(target, |component| match component {
sdf::PathComponent::Prim(name) => {
let path = cursor.append_path(name).expect("name validated as an identifier");
elems.push(ChainElement {
path: path.clone(),
spec_type: sdf::SpecType::Prim,
child_key: sdf::ChildrenKey::PrimChildren,
child_name: name.to_owned(),
});
cursor = path;
}
sdf::PathComponent::Variant { set, selection } => {
elems.push(ChainElement {
path: cursor.append_variant_selection(set, ""),
spec_type: sdf::SpecType::VariantSet,
child_key: sdf::ChildrenKey::VariantSetChildren,
child_name: set.to_owned(),
});
let variant_path = cursor.append_variant_selection(set, selection);
elems.push(ChainElement {
path: variant_path.clone(),
spec_type: sdf::SpecType::Variant,
child_key: sdf::ChildrenKey::VariantChildren,
child_name: selection.to_owned(),
});
cursor = variant_path;
}
})?;
Ok(elems)
}
fn add_to_token_vec(
data: &mut dyn sdf::AbstractData,
owner_path: &sdf::Path,
key: sdf::ChildrenKey,
name: &str,
) -> Result<(), sdf::AuthoringError> {
let existing = try_child_field(data, owner_path, key)?.map(Cow::into_owned);
match existing {
Some(sdf::Value::TokenVec(mut v)) => {
if !v.iter().any(|n| *n == name) {
v.push(name.into());
data.set_field(owner_path, key.as_str(), sdf::Value::TokenVec(v));
}
}
Some(_) => {
return Err(sdf::AuthoringError::InvalidPath {
path: owner_path.clone(),
reason: "child-list field exists with non-TokenVec value",
});
}
None => {
data.set_field(owner_path, key.as_str(), sdf::Value::TokenVec(vec![name.into()]));
}
}
Ok(())
}
fn remove_from_token_vec(
data: &mut dyn sdf::AbstractData,
owner_path: &sdf::Path,
key: sdf::ChildrenKey,
name: &str,
) -> Result<(), sdf::AuthoringError> {
let Some(value) = try_child_field(data, owner_path, key)? else {
return Ok(());
};
let sdf::Value::TokenVec(mut v) = value.into_owned() else {
return Ok(());
};
let Some(idx) = v.iter().position(|n| *n == name) else {
return Ok(());
};
v.remove(idx);
if v.is_empty() {
data.erase_field(owner_path, key.as_str());
} else {
data.set_field(owner_path, key.as_str(), sdf::Value::TokenVec(v));
}
Ok(())
}
pub(crate) fn remove_spec(data: &mut dyn sdf::AbstractData, path: &sdf::Path) -> Result<bool, sdf::AuthoringError> {
let (owner, name, child_key) = match data.spec_type(path) {
Some(sdf::SpecType::Prim) => {
for descendant in data.spec_paths() {
if descendant != *path && descendant.has_prefix(path) {
data.erase_spec(&descendant);
}
}
let Some(name) = path.name() else {
return Ok(false);
};
(
path.parent().unwrap_or_else(sdf::Path::abs_root),
name.to_owned(),
sdf::ChildrenKey::PrimChildren,
)
}
Some(sdf::SpecType::Attribute | sdf::SpecType::Relationship) => {
let Some((owner, name)) = path.split_property() else {
return Ok(false);
};
(owner, name.to_owned(), sdf::ChildrenKey::PropertyChildren)
}
_ => return Ok(false),
};
data.erase_spec(path);
remove_from_token_vec(data, &owner, child_key, &name)?;
Ok(true)
}
fn validate_token_vec(
data: &dyn sdf::AbstractData,
path: &sdf::Path,
key: sdf::ChildrenKey,
) -> Result<(), sdf::AuthoringError> {
match try_child_field(data, path, key)? {
Some(value) if !matches!(&*value, sdf::Value::TokenVec(_)) => Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "child-list field exists with non-TokenVec value",
}),
_ => Ok(()),
}
}
fn try_child_field<'a>(
data: &'a dyn sdf::AbstractData,
path: &sdf::Path,
key: sdf::ChildrenKey,
) -> Result<Option<Cow<'a, sdf::Value>>, sdf::AuthoringError> {
Ok(data.try_field(path, key.as_str())?)
}
fn require_spec_type_or_absent(
data: &dyn sdf::AbstractData,
path: &sdf::Path,
expected: sdf::SpecType,
) -> Result<(), sdf::AuthoringError> {
match data.spec_type(path) {
Some(existing) if existing != expected => Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "spec exists with the wrong SpecType",
}),
_ => Ok(()),
}
}
fn require_prim_path(path: &sdf::Path) -> Result<(), sdf::AuthoringError> {
parse_prim_path(path, |_| {})
}
fn require_prim_leaf(path: &sdf::Path) -> Result<(), sdf::AuthoringError> {
if path.is_prim_variant_selection_path() {
return Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "expected a prim path, but the leaf is a variant selection",
});
}
Ok(())
}
fn split_property_path(path: &sdf::Path) -> Result<(sdf::Path, String), sdf::AuthoringError> {
let (prim_path, suffix) = path.split_property().ok_or(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "expected property path",
})?;
require_prim_path(&prim_path)?;
if !suffix.split(':').all(sdf::Path::is_valid_identifier) {
return Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "property name must be a colon-separated identifier",
});
}
Ok((prim_path, suffix.to_owned()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdf::{AbstractData, Data};
fn data_with_spec(path: &str, ty: sdf::SpecType) -> (Data, sdf::Path) {
let path = sdf::path(path).expect("valid path");
let mut data = Data::new();
data.create_spec(path.clone(), ty);
(data, path)
}
#[test]
fn prim_mut_reads() {
let (mut data, path) = data_with_spec("/p", sdf::SpecType::Prim);
let mut prim = PrimSpecMut::get(&mut data, path.clone()).expect("prim spec");
prim.set_type_name("Xform");
prim.set_specifier(sdf::Specifier::Def);
assert_eq!(prim.type_name(), Some(tf::Token::from("Xform")));
assert_eq!(prim.specifier(), Some(sdf::Specifier::Def));
}
#[test]
fn add_api_schema_prepends() -> Result<(), SpecError> {
let (mut data, path) = data_with_spec("/p", sdf::SpecType::Prim);
let mut prim = PrimSpecMut::get(&mut data, path).expect("prim spec");
assert!(prim.add_applied_schema("MaterialBindingAPI")?);
assert!(prim.add_applied_schema("SkelBindingAPI")?);
assert!(!prim.add_applied_schema("MaterialBindingAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert!(!op.explicit);
assert_eq!(
op.prepended_items,
vec![tf::Token::from("MaterialBindingAPI"), tf::Token::from("SkelBindingAPI")]
);
Ok(())
}
#[test]
fn add_connection_path_dedups() {
let (mut data, path) = data_with_spec("/A.in", sdf::SpecType::Attribute);
let mut attr = AttributeSpecMut::get(&mut data, path).expect("attr spec");
let target = sdf::Path::new("/A.out").expect("path");
assert!(attr.add_connection_path(target.clone(), false));
assert!(!attr.add_connection_path(target, false));
}
#[test]
fn clear_connection_paths_noop() {
let (mut data, path) = data_with_spec("/A.in", sdf::SpecType::Attribute);
let mut attr = AttributeSpecMut::get(&mut data, path).expect("attr spec");
assert!(!attr.clear_connection_paths());
attr.add_connection_path(sdf::Path::new("/A.out").expect("path"), false);
assert!(attr.clear_connection_paths());
assert!(!attr.clear_connection_paths());
}
#[test]
fn add_api_schema_explicit() -> Result<(), SpecError> {
let (mut data, path) = data_with_spec("/p", sdf::SpecType::Prim);
data.set_field(
&path,
sdf::FieldKey::ApiSchemas.as_str(),
sdf::Value::TokenListOp(sdf::TokenListOp::explicit([tf::Token::from("ExistingAPI")])),
);
let mut prim = PrimSpecMut::get(&mut data, path).expect("prim spec");
assert!(prim.add_applied_schema("NewAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert!(op.explicit);
assert_eq!(
op.explicit_items,
vec![tf::Token::from("ExistingAPI"), tf::Token::from("NewAPI")]
);
Ok(())
}
#[test]
fn add_api_schema_keeps_add() -> Result<(), SpecError> {
let (mut data, path) = data_with_spec("/p", sdf::SpecType::Prim);
data.set_field(
&path,
sdf::FieldKey::ApiSchemas.as_str(),
sdf::Value::TokenListOp(sdf::TokenListOp {
added_items: vec![tf::Token::from("ExistingAPI")],
..Default::default()
}),
);
let mut prim = PrimSpecMut::get(&mut data, path).expect("prim spec");
assert!(!prim.add_applied_schema("ExistingAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert_eq!(op.added_items, vec![tf::Token::from("ExistingAPI")]);
assert!(op.prepended_items.is_empty());
Ok(())
}
#[test]
fn add_api_schema_clears_delete() -> Result<(), SpecError> {
let (mut data, path) = data_with_spec("/p", sdf::SpecType::Prim);
data.set_field(
&path,
sdf::FieldKey::ApiSchemas.as_str(),
sdf::Value::TokenListOp(sdf::TokenListOp {
deleted_items: vec![tf::Token::from("RemovedAPI")],
..Default::default()
}),
);
let mut prim = PrimSpecMut::get(&mut data, path).expect("prim spec");
assert!(prim.add_applied_schema("RemovedAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert_eq!(op.prepended_items, vec![tf::Token::from("RemovedAPI")]);
assert!(op.deleted_items.is_empty());
Ok(())
}
#[test]
fn add_api_schema_stale_added() -> Result<(), SpecError> {
let (mut data, path) = data_with_spec("/p", sdf::SpecType::Prim);
data.set_field(
&path,
sdf::FieldKey::ApiSchemas.as_str(),
sdf::Value::TokenListOp(sdf::TokenListOp {
explicit: true,
added_items: vec![tf::Token::from("StaleAPI")],
..Default::default()
}),
);
let mut prim = PrimSpecMut::get(&mut data, path).expect("prim spec");
assert!(prim.add_applied_schema("StaleAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert!(op.explicit);
assert_eq!(op.explicit_items, vec![tf::Token::from("StaleAPI")]);
Ok(())
}
#[test]
fn add_api_schema_dup_delete() -> Result<(), SpecError> {
let (mut data, path) = data_with_spec("/p", sdf::SpecType::Prim);
data.set_field(
&path,
sdf::FieldKey::ApiSchemas.as_str(),
sdf::Value::TokenListOp(sdf::TokenListOp {
deleted_items: vec![tf::Token::from("RemovedAPI"), tf::Token::from("RemovedAPI")],
..Default::default()
}),
);
let mut prim = PrimSpecMut::get(&mut data, path).expect("prim spec");
assert!(prim.add_applied_schema("RemovedAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert!(op.deleted_items.is_empty());
assert_eq!(op.prepended_items, vec![tf::Token::from("RemovedAPI")]);
Ok(())
}
#[test]
fn add_api_schema_rejects_wrong_type() {
let (mut data, path) = data_with_spec("/p", sdf::SpecType::Prim);
data.set_field(
&path,
sdf::FieldKey::ApiSchemas.as_str(),
sdf::Value::token_vec(["ExistingAPI"]),
);
let mut prim = PrimSpecMut::get(&mut data, path).expect("prim spec");
assert!(matches!(
prim.add_applied_schema("NewAPI"),
Err(SpecError::FieldType {
field: "apiSchemas",
expected: "sdf::TokenListOp"
})
));
}
#[test]
fn attribute_mut_reads() {
let (mut data, path) = data_with_spec("/A.x", sdf::SpecType::Attribute);
let mut attr = AttributeSpecMut::get(&mut data, path).expect("attribute spec");
attr.set_default(sdf::Value::Int(42));
attr.set_custom(true);
assert_eq!(attr.default(), Some(sdf::Value::Int(42)));
assert!(attr.is_custom());
}
#[test]
fn relationship_mut_reads() {
let (mut data, path) = data_with_spec("/A.rel", sdf::SpecType::Relationship);
let mut rel = RelationshipSpecMut::get(&mut data, path).expect("relationship spec");
let target = sdf::Path::new("/Target").expect("valid path");
rel.add_target(target.clone());
assert_eq!(
rel.target_path_list().and_then(|op| op.iter().next().cloned()),
Some(target)
);
}
#[test]
fn remove_target_suppresses_weaker() {
let (mut data, path) = data_with_spec("/A.rel", sdf::SpecType::Relationship);
let mut rel = RelationshipSpecMut::get(&mut data, path).expect("relationship spec");
let target = sdf::Path::new("/Target").expect("valid path");
assert!(rel.remove_target(&target));
let stronger = rel.target_path_list().expect("target list op");
assert!(stronger.deleted_items.contains(&target));
let weaker = sdf::PathListOp::explicit([target.clone(), sdf::Path::new("/Keep").unwrap()]);
let composed: Vec<_> = stronger.combined_with(&weaker).iter().cloned().collect();
assert_eq!(composed, vec![sdf::Path::new("/Keep").unwrap()]);
}
#[test]
fn remove_target_explicit_drops_entry() {
let (mut data, path) = data_with_spec("/A.rel", sdf::SpecType::Relationship);
let mut rel = RelationshipSpecMut::get(&mut data, path).expect("relationship spec");
let a = sdf::Path::new("/A").unwrap();
let b = sdf::Path::new("/B").unwrap();
rel.add_target(a.clone());
rel.add_target(b.clone());
assert!(rel.remove_target(&a));
let op = rel.target_path_list().expect("target list op");
assert!(op.explicit);
assert!(op.deleted_items.is_empty());
assert_eq!(op.iter().cloned().collect::<Vec<_>>(), vec![b]);
}
#[test]
fn remove_target_reports_change() {
let x = sdf::Path::new("/X").unwrap();
let mut op = sdf::PathListOp::added([x.clone()]);
op.deleted_items.push(x.clone());
let (mut data, path) = data_with_spec("/A.rel", sdf::SpecType::Relationship);
data.set_field(&path, sdf::FieldKey::TargetPaths.as_str(), sdf::Value::PathListOp(op));
let mut rel = RelationshipSpecMut::get(&mut data, path).expect("relationship spec");
assert!(rel.remove_target(&x));
let op = rel.target_path_list().expect("target list op");
assert!(op.added_items.is_empty());
assert!(op.deleted_items.contains(&x));
}
#[test]
fn pseudo_root_mut_reads() {
let mut data = Data::new();
data.create_spec(sdf::Path::abs_root(), sdf::SpecType::PseudoRoot);
let mut root = PseudoRootSpecMut::get(&mut data).expect("pseudo-root spec");
root.set_default_prim("World");
assert_eq!(root.default_prim(), Some(tf::Token::from("World")));
}
#[test]
fn insert_sublayer_aligns_offsets() {
let mut data = Data::new();
data.create_spec(sdf::Path::abs_root(), sdf::SpecType::PseudoRoot);
let mut root = PseudoRootSpecMut::get(&mut data).expect("pseudo-root spec");
root.set_sublayers(["b.usda"]);
root.insert_sublayer(0, "a.usda", sdf::LayerOffset::new(10.0, 1.0));
assert_eq!(root.sublayers(), Some(vec!["a.usda".to_string(), "b.usda".to_string()]));
let offsets = root
.field(sdf::FieldKey::SubLayerOffsets.as_str())
.ok()
.flatten()
.expect("offsets authored")
.try_as_layer_offset_vec()
.expect("layer-offset vec");
assert_eq!(
offsets,
vec![sdf::LayerOffset::new(10.0, 1.0), sdf::LayerOffset::IDENTITY]
);
}
#[test]
fn remove_sublayer_drops_aligned() {
let mut data = Data::new();
data.create_spec(sdf::Path::abs_root(), sdf::SpecType::PseudoRoot);
let mut root = PseudoRootSpecMut::get(&mut data).expect("pseudo-root spec");
root.insert_sublayer(0, "a.usda", sdf::LayerOffset::new(10.0, 1.0));
root.insert_sublayer(1, "b.usda", sdf::LayerOffset::IDENTITY);
assert!(root.remove_sublayer("a.usda"));
assert!(!root.remove_sublayer("missing.usda"));
assert_eq!(root.sublayers(), Some(vec!["b.usda".to_string()]));
let offsets = root
.field(sdf::FieldKey::SubLayerOffsets.as_str())
.ok()
.flatten()
.expect("offsets authored")
.try_as_layer_offset_vec()
.expect("layer-offset vec");
assert_eq!(offsets, vec![sdf::LayerOffset::IDENTITY]);
}
}