use std::cell::RefCell;
use std::fmt;
use std::sync::Arc;
use crate::{pcp, sdf, tf};
use super::attribute::AttributeAuthoringPlan;
use super::{
Attribute, AttributeBuilder, EditTarget, Prim, PrimTypeInfo, Relationship, RelationshipBuilder, SpecSite, Stage,
StageAuthoringError,
};
pub struct StageEdit {
stage: Stage,
target: EditTarget,
queued: RefCell<Vec<PlannedProperty>>,
}
impl fmt::Debug for StageEdit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StageEdit").field("queued", &self.queued).finish()
}
}
impl StageEdit {
pub(super) fn new(stage: &Stage) -> Self {
StageEdit {
stage: stage.clone(),
target: stage.edit_target(),
queued: RefCell::new(Vec::new()),
}
}
pub fn prim(&self, path: impl sdf::IntoPath) -> Result<PrimEdit<'_>, sdf::PathParseError> {
Ok(PrimEdit {
batch: self,
prim: self.stage.prim(path)?,
})
}
pub fn attribute_builder(
&self,
path: impl sdf::IntoPath,
type_name: impl Into<sdf::ValueTypeName>,
) -> AttributeBuilder<'_> {
self.stage.attribute_builder(path, type_name).in_batch(self)
}
pub fn relationship_builder(&self, path: impl sdf::IntoPath) -> RelationshipBuilder<'_> {
self.stage.relationship_builder(path).in_batch(self)
}
pub fn group<T, E>(&self, f: impl FnOnce(&StageEdit) -> Result<T, E>) -> Result<T, E>
where
E: From<StageAuthoringError>,
{
let group = StageEdit {
stage: self.stage.clone(),
target: self.target.clone(),
queued: RefCell::new(Vec::new()),
};
let value = f(&group)?;
self.absorb(group.queued.into_inner())?;
Ok(value)
}
fn absorb(&self, properties: Vec<PlannedProperty>) -> Result<(), StageAuthoringError> {
let mut queued = self.queued.borrow_mut();
let repeated = properties
.iter()
.find(|property| queued.iter().any(|other| other.path == property.path));
if let Some(repeated) = repeated {
return Err(StageAuthoringError::DuplicateProperty {
path: repeated.path.clone(),
});
}
queued.extend(properties);
Ok(())
}
pub(super) fn queue(&self, planned: PlannedProperty) -> Result<(Stage, sdf::Path), StageAuthoringError> {
let mut queued = self.queued.borrow_mut();
if queued.iter().any(|other| other.path == planned.path) {
return Err(StageAuthoringError::DuplicateProperty { path: planned.path });
}
let site = (planned.stage.clone(), planned.path.clone());
queued.push(planned);
Ok(site)
}
pub(super) fn holds_target(&self) -> Result<(), StageAuthoringError> {
match self.stage.holds_edit_target(&self.target) {
true => Ok(()),
false => Err(StageAuthoringError::EditTargetMoved),
}
}
pub(super) fn commit(self) -> Result<(), StageAuthoringError> {
if self.queued.borrow().is_empty() {
return Ok(());
}
self.holds_target()?;
let (paths, writes): (Vec<sdf::Path>, Vec<PropertyWrite>) = self
.queued
.into_inner()
.into_iter()
.map(|property| (property.path, property.write))
.unzip();
self.stage
.with_target_layer_at_each(&paths, move |layer, spec_paths| {
for (write, spec_path) in writes.into_iter().zip(spec_paths) {
write.apply(layer.data_mut(), &spec_path)?;
}
Ok(())
})
.map(|_| ())
}
}
#[derive(Debug)]
pub struct PrimEdit<'a> {
batch: &'a StageEdit,
prim: Prim,
}
impl<'a> PrimEdit<'a> {
pub fn prim(&self) -> &Prim {
&self.prim
}
pub fn attribute_builder(
&self,
name: impl Into<tf::Token>,
type_name: impl Into<sdf::ValueTypeName>,
) -> AttributeBuilder<'a> {
self.prim.attribute_builder(name, type_name).in_batch(self.batch)
}
pub fn relationship_builder(&self, name: impl Into<tf::Token>) -> RelationshipBuilder<'a> {
self.prim.relationship_builder(name).in_batch(self.batch)
}
pub fn group<T, E>(&self, f: impl FnOnce(&PrimEdit<'_>) -> Result<T, E>) -> Result<T, E>
where
E: From<StageAuthoringError>,
{
self.batch.group(|batch| {
f(&PrimEdit {
batch,
prim: self.prim.clone(),
})
})
}
}
#[derive(Debug)]
pub(super) struct PlannedProperty {
stage: Stage,
path: sdf::Path,
write: PropertyWrite,
}
#[derive(Debug)]
pub(super) enum PropertyWrite {
Attribute {
value: AttributeWrite,
metadata: Vec<(&'static str, sdf::Value)>,
},
Relationship {
ensure: EnsurePlan,
targets: Option<Vec<sdf::Path>>,
},
}
#[derive(Debug)]
pub(super) enum AttributeWrite {
Declare(EnsurePlan),
Set(Box<AttributeAuthoringPlan>),
Block(Box<AttributeAuthoringPlan>),
}
impl PlannedProperty {
pub(super) fn new(stage: Stage, path: sdf::Path, write: PropertyWrite) -> Self {
PlannedProperty { stage, path, write }
}
pub(super) fn commit(self) -> Result<(Stage, sdf::Path), StageAuthoringError> {
let PlannedProperty { stage, path, write } = self;
stage.with_target_layer_at(&path, move |layer, spec_path| write.apply(layer.data_mut(), &spec_path))?;
Ok((stage, path))
}
}
impl PropertyWrite {
fn apply(self, data: &mut dyn sdf::AbstractData, path: &sdf::Path) -> Result<(), StageAuthoringError> {
match self {
Self::Attribute { value, metadata } => {
match value {
AttributeWrite::Declare(ensure) => apply_plan(data, path, Attribute::KIND, &ensure)?,
AttributeWrite::Set(plan) => plan.write(data, path)?,
AttributeWrite::Block(plan) => plan.block(data, path)?,
}
if metadata.is_empty() {
return Ok(());
}
edit_spec(data, path.clone(), Attribute::KIND, Attribute::view, |spec| {
for (key, value) in metadata {
spec.set(key, value);
}
Ok(())
})
}
Self::Relationship { ensure, targets } => {
apply_plan(data, path, Relationship::KIND, &ensure)?;
let Some(targets) = targets else {
return Ok(());
};
edit_spec(data, path.clone(), Relationship::KIND, Relationship::view, |spec| {
Ok(spec.set_target_paths(targets)?)
})
}
}
}
}
pub(super) trait PropertySpecKind {
const KIND: sdf::SpecType;
type View<'a>;
fn view(data: &mut dyn sdf::AbstractData, path: sdf::Path) -> Option<Self::View<'_>>;
}
impl PropertySpecKind for Attribute {
const KIND: sdf::SpecType = sdf::SpecType::Attribute;
type View<'a> = sdf::AttributeSpecMut<'a>;
fn view(data: &mut dyn sdf::AbstractData, path: sdf::Path) -> Option<Self::View<'_>> {
sdf::AttributeSpecMut::get(data, path)
}
}
impl PropertySpecKind for Relationship {
const KIND: sdf::SpecType = sdf::SpecType::Relationship;
type View<'a> = sdf::RelationshipSpecMut<'a>;
fn view(data: &mut dyn sdf::AbstractData, path: sdf::Path) -> Option<Self::View<'_>> {
sdf::RelationshipSpecMut::get(data, path)
}
}
pub(super) fn author<P: PropertySpecKind>(
stage: &Stage,
path: &sdf::Path,
edit: impl FnOnce(&mut P::View<'_>) -> Result<(), StageAuthoringError>,
) -> Result<(), StageAuthoringError> {
let plan = plan_property_spec(stage, path, P::KIND, None)?;
stage
.with_target_layer_at(path, |layer, spec_path| {
apply_plan(layer.data_mut(), &spec_path, P::KIND, &plan)?;
edit_spec(layer.data_mut(), spec_path, P::KIND, P::view, edit)
})
.map(|_| ())
}
pub(super) fn edit_existing<P: PropertySpecKind>(
stage: &Stage,
path: &sdf::Path,
edit: impl FnOnce(&mut P::View<'_>) -> Result<(), StageAuthoringError>,
) -> Result<(), StageAuthoringError> {
stage.require_included(path)?;
stage
.with_target_layer_at(path, |layer, spec_path| {
edit_existing_spec(layer.data_mut(), spec_path, P::KIND, P::view, edit)
})
.map(|_| ())
}
#[derive(Debug, Clone, PartialEq)]
pub(super) enum PropertyDeclaration {
Attribute {
type_name: sdf::ValueTypeName,
variability: sdf::Variability,
custom: bool,
},
Relationship {
variability: sdf::Variability,
custom: bool,
},
}
impl PropertyDeclaration {
pub fn kind(&self) -> sdf::SpecType {
match self {
Self::Attribute { .. } => sdf::SpecType::Attribute,
Self::Relationship { .. } => sdf::SpecType::Relationship,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) enum EnsurePlan {
Existing,
Stamp(PropertyDeclaration),
}
impl EnsurePlan {
pub fn attribute_type(&self) -> Option<&sdf::ValueTypeName> {
match self {
Self::Stamp(PropertyDeclaration::Attribute { type_name, .. }) => Some(type_name),
Self::Existing | Self::Stamp(PropertyDeclaration::Relationship { .. }) => None,
}
}
}
pub(super) fn plan_property_spec(
stage: &Stage,
path: &sdf::Path,
kind: sdf::SpecType,
fallback: Option<PropertyDeclaration>,
) -> Result<EnsurePlan, StageAuthoringError> {
stage.require_included(path)?;
if let Some(found) = stage.local_spec_type(path)? {
return if found == kind {
Ok(EnsurePlan::Existing)
} else {
Err(kind_mismatch(path, kind, found))
};
}
let Some((info, name)) = schema_definition(stage, path)? else {
return Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "property authoring needs a property path",
}
.into());
};
if let Some(property) = info.prim_definition().property(&name) {
let found = property.spec_type();
if found != kind {
return Err(kind_mismatch(path, kind, found));
}
let declaration = if kind == sdf::SpecType::Attribute {
PropertyDeclaration::Attribute {
type_name: property
.type_name_token()
.map(sdf::ValueTypeName::from)
.ok_or(sdf::ValueTypeError::Empty)?,
variability: property.variability(),
custom: false,
}
} else {
PropertyDeclaration::Relationship {
variability: property.variability(),
custom: false,
}
};
return Ok(EnsurePlan::Stamp(declaration));
}
let strongest = stage
.masked_opinions(path, |graph, cache| cache.property_stack(graph, path, None))?
.into_iter()
.next();
if let Some(site) = strongest {
let declaration = declaration_at(stage, &site, kind)?;
if declaration.kind() != kind {
return Err(kind_mismatch(path, kind, declaration.kind()));
}
return Ok(EnsurePlan::Stamp(declaration));
}
fallback.map(EnsurePlan::Stamp).ok_or_else(|| missing_spec(path, kind))
}
pub(super) fn apply_plan(
data: &mut dyn sdf::AbstractData,
path: &sdf::Path,
kind: sdf::SpecType,
plan: &EnsurePlan,
) -> Result<(), StageAuthoringError> {
match data.spec_type(path) {
Some(found) if found == kind => Ok(()),
Some(found) => Err(kind_mismatch(path, kind, found)),
None => match plan {
EnsurePlan::Existing => Err(missing_spec(path, kind)),
EnsurePlan::Stamp(declaration) => stamp(data, path, declaration),
},
}
}
pub(super) fn schema_definition(
stage: &Stage,
path: &sdf::Path,
) -> Result<Option<(Arc<PrimTypeInfo>, tf::Token)>, pcp::QueryError> {
let Some((prim, name)) = path.split_property() else {
return Ok(None);
};
Ok(Some((stage.prim_type_info_composed(prim)?, tf::Token::from(name))))
}
pub(super) fn schema_property_field(
stage: &Stage,
path: &sdf::Path,
key: &str,
) -> Result<Option<sdf::Value>, pcp::QueryError> {
let Some((info, name)) = schema_definition(stage, path)? else {
return Ok(None);
};
Ok(info
.prim_definition()
.property(&name)
.and_then(|property| property.field(key).cloned()))
}
pub(super) fn edit_spec<'a, S>(
data: &'a mut dyn sdf::AbstractData,
path: sdf::Path,
kind: sdf::SpecType,
get: impl FnOnce(&'a mut dyn sdf::AbstractData, sdf::Path) -> Option<S>,
f: impl FnOnce(&mut S) -> Result<(), StageAuthoringError>,
) -> Result<(), StageAuthoringError> {
match get(data, path.clone()) {
Some(mut spec) => f(&mut spec),
None => Err(missing_spec(&path, kind)),
}
}
pub(super) fn edit_existing_spec<'a, S>(
data: &'a mut dyn sdf::AbstractData,
path: sdf::Path,
kind: sdf::SpecType,
get: impl FnOnce(&'a mut dyn sdf::AbstractData, sdf::Path) -> Option<S>,
f: impl FnOnce(&mut S) -> Result<(), StageAuthoringError>,
) -> Result<(), StageAuthoringError> {
match data.spec_type(&path) {
None => Ok(()),
Some(found) if found == kind => {
let mut spec = get(data, path).expect("the kind was checked");
f(&mut spec)
}
Some(found) => Err(kind_mismatch(&path, kind, found)),
}
}
pub(super) fn check_reserved(kind: sdf::SpecType, key: &'static str) -> Result<(), StageAuthoringError> {
let reserved: &[sdf::FieldKey] = match kind {
sdf::SpecType::Attribute => &[
sdf::FieldKey::Default,
sdf::FieldKey::TimeSamples,
sdf::FieldKey::TypeName,
sdf::FieldKey::ConnectionPaths,
sdf::FieldKey::Variability,
sdf::FieldKey::Custom,
],
sdf::SpecType::Relationship => &[
sdf::FieldKey::TargetPaths,
sdf::FieldKey::Variability,
sdf::FieldKey::Custom,
],
_ => &[],
};
if reserved.iter().any(|field| field.as_str() == key) {
return Err(StageAuthoringError::ReservedField { field: key });
}
Ok(())
}
fn declaration_at(
stage: &Stage,
site: &SpecSite,
wanted: sdf::SpecType,
) -> Result<PropertyDeclaration, StageAuthoringError> {
let layer = stage
.layer(&site.layer)
.ok_or_else(|| StageAuthoringError::LayerNotFound {
layer: site.layer.clone(),
})?;
declaration_of(layer.data(), &site.path, wanted)
}
fn declaration_of(
data: &dyn sdf::AbstractData,
path: &sdf::Path,
wanted: sdf::SpecType,
) -> Result<PropertyDeclaration, StageAuthoringError> {
match data.spec_type(path) {
Some(sdf::SpecType::Attribute) => {
let spec = sdf::AttributeSpecRef::get(data, path.clone()).expect("the kind was checked");
Ok(PropertyDeclaration::Attribute {
type_name: spec.declared_type()?.ok_or(sdf::ValueTypeError::Empty)?,
variability: spec
.typed_field(sdf::FieldKey::Variability, "Variability")?
.unwrap_or_default(),
custom: spec.typed_field(sdf::FieldKey::Custom, "Bool")?.unwrap_or(false),
})
}
Some(sdf::SpecType::Relationship) => {
let spec = sdf::RelationshipSpecRef::get(data, path.clone()).expect("the kind was checked");
Ok(PropertyDeclaration::Relationship {
variability: spec
.typed_field(sdf::FieldKey::Variability, "Variability")?
.unwrap_or_default(),
custom: spec.typed_field(sdf::FieldKey::Custom, "Bool")?.unwrap_or(false),
})
}
Some(found) => Err(kind_mismatch(path, wanted, found)),
None => Err(missing_spec(path, wanted)),
}
}
fn stamp(
data: &mut dyn sdf::AbstractData,
path: &sdf::Path,
declaration: &PropertyDeclaration,
) -> Result<(), StageAuthoringError> {
match declaration {
PropertyDeclaration::Attribute {
type_name,
variability,
custom,
} => {
sdf::AttributeSpec::new(data, path.clone(), type_name.clone(), *variability, *custom)?;
}
PropertyDeclaration::Relationship { variability, custom } => {
sdf::RelationshipSpec::new(data, path.clone(), *variability, *custom)?;
}
}
Ok(())
}
fn kind_mismatch(path: &sdf::Path, expected: sdf::SpecType, found: sdf::SpecType) -> StageAuthoringError {
StageAuthoringError::SpecKindMismatch {
path: path.clone(),
expected,
found,
}
}
pub(super) fn missing_spec(path: &sdf::Path, kind: sdf::SpecType) -> StageAuthoringError {
let reason = match kind {
sdf::SpecType::Prim => "no prim spec at path on the edit target layer",
sdf::SpecType::Attribute => "no attribute spec at path on the edit target layer",
sdf::SpecType::Relationship => "no relationship spec at path on the edit target layer",
_ => "no spec at path on the edit target layer",
};
sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason,
}
.into()
}
#[cfg(test)]
mod tests {
use crate::Result;
use crate::sdf;
use crate::usd::{EditTarget, Stage, StageAuthoringError, TimeCode};
fn stage() -> Result<Stage> {
Stage::builder().in_memory("anon.usda")
}
fn variant_target(stage: &Stage) -> Result<EditTarget, sdf::PathParseError> {
let root = stage.edit_target().layer_identifier().to_string();
EditTarget::for_local_direct_variant(root, "/World{set=sel}")
}
#[test]
fn batch_authors_together() -> Result<()> {
let stage = stage()?;
stage.define_prim("/World")?;
let (height, proxy) = stage.edit(|edit| {
let world = edit.prim("/World")?;
let height = world.attribute_builder("height", "double").set(2.0).build()?;
let proxy = world
.relationship_builder("proxy")
.custom(false)
.set_targets(["/World"])
.build()?;
Ok::<_, StageAuthoringError>((height, proxy))
})?;
assert_eq!(height.get::<f64>()?, Some(2.0));
assert_eq!(proxy.targets()?, vec![sdf::Path::new("/World")?]);
assert!(!proxy.is_custom()?);
Ok(())
}
#[test]
fn batch_takes_property_paths() -> Result<()> {
let stage = stage()?;
stage.define_prim("/World")?;
let height = stage.edit(|edit| edit.attribute_builder("/World.height", "double").set(2.0).build())?;
assert_eq!(height.get::<f64>()?, Some(2.0));
Ok(())
}
#[test]
fn prim_builder_commits_now() -> Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?;
let failed = stage.edit(|edit| {
prim.attribute_builder("height", "double").set(2.0).build()?;
edit.prim("/World")?
.attribute_builder("radius", "double")
.set(1.0)
.build()?;
Err::<(), _>(StageAuthoringError::OutsideEditTarget {
path: sdf::Path::new("/World.radius")?,
})
});
assert!(failed.is_err());
assert!(prim.attribute("height").is_defined()?);
assert!(!prim.attribute("radius").is_defined()?);
Ok(())
}
#[test]
fn batch_error_authors_none() -> Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?;
let failed = stage.edit(|edit| {
edit.prim("/World")?
.attribute_builder("height", "double")
.set(2.0)
.build()?;
Err::<(), _>(StageAuthoringError::OutsideEditTarget {
path: sdf::Path::new("/World.height")?,
})
});
assert!(matches!(failed, Err(StageAuthoringError::OutsideEditTarget { .. })));
assert!(!prim.attribute("height").is_defined()?);
Ok(())
}
#[test]
fn duplicate_property_rejected() -> Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?;
let failed = stage.edit(|edit| {
let world = edit.prim("/World")?;
world.attribute_builder("height", "double").set(2.0).build()?;
world.attribute_builder("height", "double").set(3.0).build()
});
assert!(matches!(failed, Err(StageAuthoringError::DuplicateProperty { .. })));
assert!(!prim.attribute("height").is_defined()?);
Ok(())
}
#[test]
fn target_move_rejected() -> Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?;
let variant = variant_target(&stage)?;
let failed = stage.edit(|edit| {
edit.prim("/World")?
.attribute_builder("height", "double")
.set_at(2.0, TimeCode::new(15.0))
.build()?;
stage.set_edit_target(variant.clone())?;
Ok(())
});
assert!(matches!(failed, Err(StageAuthoringError::EditTargetMoved)));
assert!(!prim.attribute("height").is_defined()?);
Ok(())
}
#[test]
fn target_move_rejects_queue() -> Result<()> {
let stage = stage()?;
stage.define_prim("/World")?;
let variant = variant_target(&stage)?;
let failed = stage.edit(|edit| {
stage.set_edit_target(variant.clone())?;
edit.prim("/World")?
.attribute_builder("height", "double")
.set(2.0)
.build()
});
assert!(matches!(failed, Err(StageAuthoringError::EditTargetMoved)));
Ok(())
}
#[derive(Debug)]
enum CallerError {
Refused,
Stage(StageAuthoringError),
}
impl From<StageAuthoringError> for CallerError {
fn from(error: StageAuthoringError) -> Self {
Self::Stage(error)
}
}
#[test]
fn edit_carries_caller_error() -> Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?;
let failed = stage.edit(|edit| {
edit.prim("/World")
.map_err(StageAuthoringError::from)?
.attribute_builder("height", "double")
.set(2.0)
.build()?;
Err::<(), _>(CallerError::Refused)
});
assert!(matches!(failed, Err(CallerError::Refused)));
assert!(!prim.attribute("height").is_defined()?);
Ok(())
}
#[test]
fn commit_error_converts() -> Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?;
let variant = variant_target(&stage)?;
let failed = stage.edit(|edit| {
edit.attribute_builder("/World.height", "double").set(2.0).build()?;
stage.set_edit_target(variant.clone())?;
Ok::<_, CallerError>(())
});
assert!(matches!(
failed,
Err(CallerError::Stage(StageAuthoringError::EditTargetMoved))
));
assert!(!prim.attribute("height").is_defined()?);
Ok(())
}
#[test]
fn group_joins_batch() -> Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?;
stage.edit(|edit| {
let world = edit.prim("/World")?;
world.attribute_builder("radius", "double").set(1.0).build()?;
world.group(|group| {
group.attribute_builder("height", "double").set(2.0).build()?;
group.attribute_builder("width", "double").set(3.0).build()?;
Ok::<_, StageAuthoringError>(())
})?;
assert!(!prim.attribute("height").is_defined()?, "nothing is written yet");
Ok::<_, crate::Error>(())
})?;
for (name, value) in [("radius", 1.0), ("height", 2.0), ("width", 3.0)] {
assert_eq!(prim.attribute(name).get::<f64>()?, Some(value), "{name}");
}
Ok(())
}
#[test]
fn group_error_queues_nothing() -> Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?;
stage.edit(|edit| {
let world = edit.prim("/World")?;
world.attribute_builder("radius", "double").set(1.0).build()?;
let failed = world.group(|group| {
group.attribute_builder("height", "double").set(2.0).build()?;
Err::<(), _>(CallerError::Refused)
});
assert!(matches!(failed, Err(CallerError::Refused)));
Ok::<_, StageAuthoringError>(())
})?;
assert!(prim.attribute("radius").is_defined()?);
assert!(!prim.attribute("height").is_defined()?);
Ok(())
}
#[test]
fn group_duplicate_absorbs_nothing() -> Result<()> {
let stage = stage()?;
let prim = stage.define_prim("/World")?;
stage.edit(|edit| {
let world = edit.prim("/World")?;
world.attribute_builder("height", "double").set(2.0).build()?;
let failed = world.group(|group| {
group.attribute_builder("width", "double").set(3.0).build()?;
group.attribute_builder("height", "double").set(4.0).build()?;
Ok::<_, StageAuthoringError>(())
});
assert!(matches!(failed, Err(StageAuthoringError::DuplicateProperty { .. })));
Ok::<_, StageAuthoringError>(())
})?;
assert_eq!(prim.attribute("height").get::<f64>()?, Some(2.0));
assert!(!prim.attribute("width").is_defined()?);
Ok(())
}
#[test]
fn group_shares_target() -> Result<()> {
let stage = stage()?;
stage.define_prim("/World")?;
let variant = variant_target(&stage)?;
let failed = stage.edit(|edit| {
edit.group(|group| {
stage.set_edit_target(variant.clone())?;
group.attribute_builder("/World.height", "double").set(2.0).build()
})
});
assert!(matches!(failed, Err(StageAuthoringError::EditTargetMoved)));
Ok(())
}
#[test]
fn empty_batch_authors_nothing() -> Result<()> {
let stage = stage()?;
assert_eq!(stage.edit(|_| Ok::<_, StageAuthoringError>(7))?, 7);
Ok(())
}
}