use super::{Stage, StageAuthoringError};
use crate::sdf;
#[derive(Clone)]
pub struct Prim<'s> {
stage: &'s Stage,
path: sdf::Path,
}
impl<'s> Prim<'s> {
pub(crate) fn new(stage: &'s Stage, path: sdf::Path) -> Self {
Self { stage, path }
}
pub fn path(&self) -> &sdf::Path {
&self.path
}
pub fn stage(&self) -> &'s Stage {
self.stage
}
pub fn set_type_name(self, name: impl Into<String>) -> Result<Self, StageAuthoringError> {
let name = name.into();
self.edit(&[sdf::FieldKey::TypeName], |spec| spec.set_type_name(name))
}
pub fn set_active(self, active: bool) -> Result<Self, StageAuthoringError> {
self.edit(&[sdf::FieldKey::Active], |spec| spec.set_active(active))
}
pub fn set_kind(self, kind: impl Into<String>) -> Result<Self, StageAuthoringError> {
let kind = kind.into();
self.edit(&[sdf::FieldKey::Kind], |spec| spec.set_kind(kind))
}
pub fn set_hidden(self, hidden: bool) -> Result<Self, StageAuthoringError> {
self.edit(&[sdf::FieldKey::Hidden], |spec| spec.set_hidden(hidden))
}
pub fn set_instanceable(self, instanceable: bool) -> Result<Self, StageAuthoringError> {
self.edit(&[sdf::FieldKey::Instanceable], |spec| {
spec.set_instanceable(instanceable)
})
}
pub fn add_applied_schema(self, name: impl Into<String>) -> Result<Self, StageAuthoringError> {
let path = self.path.clone();
let name = name.into();
self.stage.with_target_layer(|layer| {
let data = layer.writable_data_mut()?;
match data.spec_mut(&path).and_then(|s| s.as_prim_mut()) {
Some(mut spec) => {
spec.add_applied_schema(name)?;
let mut cl = sdf::ChangeList::new();
cl.entry_mut(&path)
.info_changed
.insert(sdf::FieldKey::ApiSchemas.as_str());
Ok(cl)
}
None => Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "no prim spec at path on the edit target layer",
}),
}
})?;
Ok(self)
}
pub fn create_attribute(
&self,
name: &str,
type_name: impl Into<String>,
) -> Result<Attribute<'s>, StageAuthoringError> {
let attr_path = self.path.append_property(name).map_err(|_| {
StageAuthoringError::Layer(sdf::AuthoringError::InvalidPath {
path: sdf::Path::from(format!("{}.{}", self.path, name).as_str()),
reason: "attribute name is not a valid property name",
})
})?;
self.stage.create_attribute(attr_path, type_name)
}
pub fn create_relationship(&self, name: &str) -> Result<Relationship<'s>, StageAuthoringError> {
let rel_path = self.path.append_property(name).map_err(|_| {
StageAuthoringError::Layer(sdf::AuthoringError::InvalidPath {
path: sdf::Path::from(format!("{}.{}", self.path, name).as_str()),
reason: "relationship name is not a valid property name",
})
})?;
self.stage.create_relationship(rel_path)
}
pub fn author_relationship_targets<I, P>(
&self,
name: &str,
targets: I,
) -> Result<Relationship<'s>, StageAuthoringError>
where
I: IntoIterator<Item = P>,
P: Into<sdf::Path>,
{
self.create_relationship(name)?
.set_custom(false)?
.set_targets(targets.into_iter().map(Into::into))
}
pub fn append_to_uniform_token_array(&self, name: &str, value: impl Into<String>) -> anyhow::Result<bool> {
let value = value.into();
let attr_path = self.path.append_property(name)?;
let existing: Vec<String> = match self.stage.field::<sdf::Value>(&attr_path, sdf::FieldKey::Default)? {
Some(sdf::Value::TokenVec(v) | sdf::Value::StringVec(v)) => v,
Some(sdf::Value::TokenListOp(op)) => op.flatten(),
Some(sdf::Value::StringListOp(op)) => op.flatten(),
_ => Vec::new(),
};
if existing.iter().any(|t| t == &value) {
return Ok(false);
}
let mut updated = existing;
updated.push(value);
self.stage
.create_attribute(attr_path, "token[]")?
.set_variability(sdf::Variability::Uniform)?
.set_custom(false)?
.set(sdf::Value::TokenVec(updated))?;
Ok(true)
}
fn edit<F>(self, fields: &[sdf::FieldKey], f: F) -> Result<Self, StageAuthoringError>
where
F: FnOnce(&mut sdf::PrimSpecMut<'_>),
{
let path = self.path.clone();
let info_changed: Vec<&'static str> = fields.iter().map(sdf::FieldKey::as_str).collect();
self.stage.with_target_layer(|layer| {
let data = layer.writable_data_mut()?;
match data.spec_mut(&path).and_then(|s| s.as_prim_mut()) {
Some(mut spec) => {
f(&mut spec);
let mut cl = sdf::ChangeList::new();
let entry = cl.entry_mut(&path);
for name in &info_changed {
entry.info_changed.insert(name);
}
Ok(cl)
}
None => Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "no prim spec at path on the edit target layer",
}),
}
})?;
Ok(self)
}
}
#[derive(Clone)]
pub struct Attribute<'s> {
stage: &'s Stage,
path: sdf::Path,
}
impl<'s> Attribute<'s> {
pub(super) fn new(stage: &'s Stage, path: sdf::Path) -> Self {
Self { stage, path }
}
pub fn path(&self) -> &sdf::Path {
&self.path
}
pub fn stage(&self) -> &'s Stage {
self.stage
}
pub fn prim(&self) -> Prim<'s> {
Prim::new(self.stage, self.path.prim_path())
}
pub fn set_variability(self, v: sdf::Variability) -> Result<Self, StageAuthoringError> {
self.edit(&[sdf::FieldKey::Variability], |spec| {
spec.add(sdf::FieldKey::Variability, sdf::Value::Variability(v))
})
}
pub fn set_custom(self, custom: bool) -> Result<Self, StageAuthoringError> {
self.edit(&[sdf::FieldKey::Custom], |spec| {
spec.add(sdf::FieldKey::Custom, sdf::Value::Bool(custom))
})
}
pub fn set(self, value: impl Into<sdf::Value>) -> Result<Self, StageAuthoringError> {
let value = value.into();
self.edit(&[sdf::FieldKey::Default], |spec| spec.set_default(value))
}
pub fn set_at(self, time: f64, value: impl Into<sdf::Value>) -> Result<Self, StageAuthoringError> {
let value = value.into();
self.edit(&[sdf::FieldKey::TimeSamples], |spec| spec.set_time_sample(time, value))
}
pub fn block(self) -> Result<Self, StageAuthoringError> {
self.edit(&[sdf::FieldKey::Default, sdf::FieldKey::TimeSamples], |spec| {
spec.set_default(sdf::Value::ValueBlock);
if let Some(sdf::Value::TimeSamples(samples)) = spec.get_mut(sdf::FieldKey::TimeSamples.as_str()) {
for (_, value) in samples.iter_mut() {
*value = sdf::Value::ValueBlock;
}
}
})
}
pub fn set_color_space(self, color_space: impl Into<String>) -> Result<Self, StageAuthoringError> {
let color_space = color_space.into();
self.edit(&[sdf::FieldKey::ColorSpace], |spec| spec.set_color_space(color_space))
}
pub fn set_metadata(self, key: &'static str, value: impl Into<sdf::Value>) -> Result<Self, StageAuthoringError> {
let value = value.into();
let path = self.path.clone();
self.stage.with_target_layer(|layer| {
let data = layer.writable_data_mut()?;
match data.spec_mut(&path).and_then(|s| s.as_attr_mut()) {
Some(mut spec) => {
spec.add(key, value);
let mut cl = sdf::ChangeList::new();
cl.entry_mut(&path).info_changed.insert(key);
Ok(cl)
}
None => Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "no attribute spec at path on the edit target layer",
}),
}
})?;
Ok(self)
}
pub fn get(&self) -> anyhow::Result<Option<sdf::Value>> {
self.stage.field::<sdf::Value>(&self.path, sdf::FieldKey::Default)
}
pub fn get_at(&self, time: f64) -> anyhow::Result<Option<sdf::Value>> {
self.stage.value_at(&self.path, time)
}
pub fn time_samples(&self) -> anyhow::Result<Option<sdf::TimeSampleMap>> {
self.stage.time_samples(&self.path)
}
fn edit<F>(self, fields: &[sdf::FieldKey], f: F) -> Result<Self, StageAuthoringError>
where
F: FnOnce(&mut sdf::AttributeSpecMut<'_>),
{
let path = self.path.clone();
let info_changed: Vec<&'static str> = fields.iter().map(sdf::FieldKey::as_str).collect();
self.stage.with_target_layer(|layer| {
let data = layer.writable_data_mut()?;
match data.spec_mut(&path).and_then(|s| s.as_attr_mut()) {
Some(mut spec) => {
f(&mut spec);
let mut cl = sdf::ChangeList::new();
let entry = cl.entry_mut(&path);
for name in &info_changed {
entry.info_changed.insert(name);
}
Ok(cl)
}
None => Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "no attribute spec at path on the edit target layer",
}),
}
})?;
Ok(self)
}
}
#[derive(Clone)]
pub struct Relationship<'s> {
stage: &'s Stage,
path: sdf::Path,
}
impl<'s> Relationship<'s> {
pub(super) fn new(stage: &'s Stage, path: sdf::Path) -> Self {
Self { stage, path }
}
pub fn path(&self) -> &sdf::Path {
&self.path
}
pub fn stage(&self) -> &'s Stage {
self.stage
}
pub fn prim(&self) -> Prim<'s> {
Prim::new(self.stage, self.path.prim_path())
}
pub fn set_variability(self, v: sdf::Variability) -> Result<Self, StageAuthoringError> {
self.edit(&[sdf::FieldKey::Variability], false, |spec| {
spec.add(sdf::FieldKey::Variability, sdf::Value::Variability(v))
})
}
pub fn set_custom(self, custom: bool) -> Result<Self, StageAuthoringError> {
self.edit(&[sdf::FieldKey::Custom], false, |spec| {
spec.add(sdf::FieldKey::Custom, sdf::Value::Bool(custom))
})
}
pub fn add_target(self, target: sdf::Path) -> Result<Self, StageAuthoringError> {
self.edit(&[sdf::FieldKey::TargetPaths], true, |spec| spec.add_target(target))
}
pub fn set_targets<I: IntoIterator<Item = sdf::Path>>(self, targets: I) -> Result<Self, StageAuthoringError> {
let targets: Vec<sdf::Path> = targets.into_iter().collect();
self.edit(&[sdf::FieldKey::TargetPaths], true, |spec| {
spec.set_target_paths(targets)
})
}
pub fn remove_target(&self, target: &sdf::Path) -> Result<bool, StageAuthoringError> {
let path = self.path.clone();
let target = target.clone();
let mut removed = false;
self.stage.with_target_layer(|layer| {
let data = layer.writable_data_mut()?;
match data.spec_mut(&path).and_then(|s| s.as_relationship_mut()) {
Some(mut spec) => {
removed = spec.remove_target(&target);
let mut cl = sdf::ChangeList::new();
if removed {
let entry = cl.entry_mut(&path);
entry.flags |= sdf::ChangeFlags::CHANGE_RELATIONSHIP_TARGETS;
entry.info_changed.insert(sdf::FieldKey::TargetPaths.as_str());
}
Ok(cl)
}
None => Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "no relationship spec at path on the edit target layer",
}),
}
})?;
Ok(removed)
}
fn edit<F>(self, fields: &[sdf::FieldKey], targets_changed: bool, f: F) -> Result<Self, StageAuthoringError>
where
F: FnOnce(&mut sdf::RelationshipSpecMut<'_>),
{
let path = self.path.clone();
let info_changed: Vec<&'static str> = fields.iter().map(sdf::FieldKey::as_str).collect();
self.stage.with_target_layer(|layer| {
let data = layer.writable_data_mut()?;
match data.spec_mut(&path).and_then(|s| s.as_relationship_mut()) {
Some(mut spec) => {
f(&mut spec);
let mut cl = sdf::ChangeList::new();
let entry = cl.entry_mut(&path);
if targets_changed {
entry.flags |= sdf::ChangeFlags::CHANGE_RELATIONSHIP_TARGETS;
}
for name in &info_changed {
entry.info_changed.insert(name);
}
Ok(cl)
}
None => Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "no relationship spec at path on the edit target layer",
}),
}
})?;
Ok(self)
}
}
#[cfg(test)]
mod tests {
use crate::sdf;
use crate::usd::Stage;
fn stage() -> anyhow::Result<Stage> {
Stage::builder().in_memory("anon.usda")
}
#[test]
fn prim_chain() -> anyhow::Result<()> {
let stage = stage()?;
stage
.define_prim("/World")?
.set_type_name("Xform")?
.set_kind("group")?
.set_active(true)?;
assert_eq!(
stage.field::<sdf::Value>("/World", sdf::FieldKey::TypeName)?,
Some(sdf::Value::Token("Xform".into())),
);
assert_eq!(stage.kind("/World")?.as_deref(), Some("group"));
Ok(())
}
#[test]
fn attribute_chain() -> anyhow::Result<()> {
let stage = stage()?;
let radius = stage
.define_prim("/Sphere")?
.set_type_name("Sphere")?
.create_attribute("radius", "double")?
.set_variability(sdf::Variability::Uniform)?
.set(sdf::Value::Double(1.5))?;
assert_eq!(radius.get()?, Some(sdf::Value::Double(1.5)));
assert_eq!(
stage.field::<sdf::Value>(radius.path(), sdf::FieldKey::Custom)?,
Some(sdf::Value::Bool(true)),
);
assert_eq!(radius.path().as_str(), "/Sphere.radius");
assert_eq!(radius.prim().path().as_str(), "/Sphere");
Ok(())
}
#[test]
fn attribute_time_samples() -> anyhow::Result<()> {
let stage = stage()?;
let attr = stage
.define_prim("/A")?
.set_type_name("Xform")?
.create_attribute("x", "double")?
.set_at(0.0, sdf::Value::Double(1.0))?
.set_at(10.0, sdf::Value::Double(3.0))?;
assert_eq!(attr.get_at(5.0)?, Some(sdf::Value::Double(2.0)));
let samples = attr.time_samples()?.expect("samples");
assert_eq!(samples.len(), 2);
Ok(())
}
#[test]
fn attribute_block() -> anyhow::Result<()> {
let stage = stage()?;
let attr = stage
.define_prim("/A")?
.set_type_name("Xform")?
.create_attribute("x", "double")?
.set(sdf::Value::Double(1.0))?
.block()?;
assert_eq!(attr.get()?, None);
assert_eq!(attr.get_at(0.0)?, None);
Ok(())
}
#[test]
fn attribute_block_clears_time_samples() -> anyhow::Result<()> {
let stage = stage()?;
let attr = stage
.define_prim("/A")?
.set_type_name("Xform")?
.create_attribute("x", "double")?
.set_at(0.0, sdf::Value::Double(1.0))?
.set_at(10.0, sdf::Value::Double(3.0))?
.block()?;
assert_eq!(attr.get_at(0.0)?, None);
assert_eq!(attr.get_at(5.0)?, None);
assert_eq!(attr.get_at(10.0)?, None);
Ok(())
}
#[test]
fn relationship_chain() -> anyhow::Result<()> {
let stage = stage()?;
let mesh = stage.define_prim("/World/Mesh")?.set_type_name("Mesh")?;
stage.define_prim("/World/Material")?.set_type_name("Material")?;
stage.define_prim("/World/Material2")?.set_type_name("Material")?;
let binding = mesh
.create_relationship("material:binding")?
.set_variability(sdf::Variability::Uniform)?
.add_target(sdf::Path::new("/World/Material")?)?
.add_target(sdf::Path::new("/World/Material2")?)?;
assert!(binding.remove_target(&sdf::Path::new("/World/Material2")?)?);
assert_eq!(stage.spec_type(binding.path())?, Some(sdf::SpecType::Relationship));
assert_eq!(
stage.field::<sdf::Value>(binding.path(), sdf::FieldKey::Custom)?,
Some(sdf::Value::Bool(true)),
);
Ok(())
}
#[test]
fn add_api_schema() -> anyhow::Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?.add_applied_schema("MaterialBindingAPI")?;
assert_eq!(stage.api_schemas(prim.path())?, vec!["MaterialBindingAPI".to_string()]);
assert!(stage.has_api_schema(prim.path(), "MaterialBindingAPI")?);
Ok(())
}
#[test]
fn add_api_schema_merges() -> anyhow::Result<()> {
let stage = stage()?;
stage.define_prim("/World")?;
stage.with_target_layer(|layer| {
let data = layer.writable_data_mut()?;
let spec = data
.spec_mut(&sdf::Path::new("/World").expect("valid path"))
.expect("prim spec");
spec.add(
sdf::FieldKey::ApiSchemas,
sdf::Value::TokenListOp(sdf::TokenListOp {
appended_items: vec!["ExistingAPI".to_string()],
..Default::default()
}),
);
let mut cl = sdf::ChangeList::new();
cl.entry_mut(&sdf::Path::new("/World").expect("valid path"))
.info_changed
.insert(sdf::FieldKey::ApiSchemas.as_str());
Ok(cl)
})?;
stage
.override_prim("/World")?
.add_applied_schema("ExistingAPI")?
.add_applied_schema("NewAPI")?;
let local = stage.field::<sdf::Value>("/World", sdf::FieldKey::ApiSchemas)?;
let Some(sdf::Value::TokenListOp(op)) = local else {
panic!("expected apiSchemas TokenListOp");
};
assert_eq!(op.appended_items, vec!["ExistingAPI".to_string()]);
assert_eq!(op.prepended_items, vec!["NewAPI".to_string()]);
Ok(())
}
}