use std::io::Cursor;
use anyhow::Result;
use super::schema::{ChildrenKey, FieldKey};
use super::{
AbstractData, AttributeSpec, AttributeSpecMut, Data, LayerData, Path, PathComponent, PrimSpec, PrimSpecMut,
PseudoRootSpec, PseudoRootSpecMut, RelationshipSpec, RelationshipSpecMut, RelocateList, Spec, SpecError, SpecType,
Specifier, Value, Variability,
};
use crate::{usda, usdc};
pub struct Layer {
pub identifier: String,
pub(crate) data: LayerData,
}
impl Layer {
pub(crate) fn new(identifier: impl Into<String>, data: LayerData) -> Self {
Self {
identifier: identifier.into(),
data,
}
}
pub fn data(&self) -> &dyn AbstractData {
self.data.as_ref()
}
pub fn identifier(&self) -> &str {
&self.identifier
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayerFormat {
Usda,
Usdc,
Usdz,
}
impl LayerFormat {
pub fn from_extension(ext: &str) -> Option<Self> {
match ext.to_ascii_lowercase().as_str() {
"usda" => Some(Self::Usda),
"usdc" | "usd" => Some(Self::Usdc),
"usdz" => Some(Self::Usdz),
_ => None,
}
}
}
impl Layer {
pub fn save(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
let path = path.as_ref();
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or_default();
let format = LayerFormat::from_extension(ext).ok_or_else(|| match ext {
"" => anyhow::anyhow!("layer path {} has no extension; cannot choose format", path.display()),
other => anyhow::anyhow!("unsupported layer extension {other:?} for save (expected usda/usdc/usd/usdz)"),
})?;
self.save_as(path, format)
}
pub fn save_as(&self, path: impl AsRef<std::path::Path>, format: LayerFormat) -> Result<()> {
let path = path.as_ref();
match format {
LayerFormat::Usda => usda::TextWriter::write_to_file(self.data.as_ref(), path),
LayerFormat::Usdc => usdc::CrateWriter::write_to_file(self.data.as_ref(), path),
LayerFormat::Usdz => {
let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("layer");
let mut buf = Vec::new();
usdc::CrateWriter::write(self.data.as_ref(), &mut Cursor::new(&mut buf))?;
let mut archive = crate::usdz::ArchiveWriter::create(path)?;
archive.add_layer(&format!("{stem}.usdc"), &buf)?;
archive.finish()?;
Ok(())
}
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum AuthoringError {
#[error(transparent)]
Spec(#[from] SpecError),
#[error("layer {identifier} is read-only; authoring is not supported")]
ReadOnly {
identifier: String,
},
#[error("path {path} is not valid here: {reason}")]
InvalidPath {
path: Path,
reason: &'static str,
},
}
impl Layer {
pub fn new_anonymous(identifier: impl Into<String>) -> Self {
let mut data = Data::new();
data.create_spec(Path::abs_root(), SpecType::PseudoRoot);
Self::new(identifier, Box::new(data))
}
pub fn create_prim(
&mut self,
path: impl Into<Path>,
specifier: Specifier,
type_name: impl Into<String>,
) -> Result<PrimSpecMut<'_>, AuthoringError> {
let path: Path = path.into();
let type_name: String = type_name.into();
require_prim_leaf(&path)?;
let data = self.writable_data_mut()?;
ensure_prim_chain(data, &path)?;
let spec = data.spec_mut(&path).expect("just ensured");
spec.add(FieldKey::Specifier, Value::Specifier(specifier));
if !type_name.is_empty() {
spec.add(FieldKey::TypeName, Value::Token(type_name));
}
Ok(spec.as_prim_mut().expect("type guaranteed by ensure_prim_chain"))
}
pub fn override_prim(&mut self, path: impl Into<Path>) -> Result<PrimSpecMut<'_>, AuthoringError> {
let path: Path = path.into();
require_prim_leaf(&path)?;
let data = self.writable_data_mut()?;
ensure_prim_chain(data, &path)?;
let spec = data.spec_mut(&path).expect("just ensured");
Ok(spec.as_prim_mut().expect("type guaranteed by ensure_prim_chain"))
}
pub fn create_attribute(
&mut self,
path: impl Into<Path>,
type_name: impl Into<String>,
variability: Variability,
custom: bool,
) -> Result<AttributeSpecMut<'_>, AuthoringError> {
let path: Path = path.into();
let type_name: String = type_name.into();
let (prim_path, property_name) = split_property_path(&path)?;
let data = self.writable_data_mut()?;
require_spec_type_or_absent(data, &path, SpecType::Attribute)?;
validate_token_vec(data, &prim_path, ChildrenKey::PropertyChildren)?;
ensure_prim_chain(data, &prim_path)?;
add_to_token_vec(
data.spec_mut(&prim_path).expect("ensure_prim_chain created it"),
&prim_path,
ChildrenKey::PropertyChildren,
&property_name,
)?;
if !data.has_spec(&path) {
data.create_spec(path.clone(), SpecType::Attribute);
}
let spec = data.spec_mut(&path).expect("just ensured");
spec.add(FieldKey::TypeName, Value::Token(type_name));
if variability != Variability::Varying {
spec.add(FieldKey::Variability, Value::Variability(variability));
} else {
spec.remove(FieldKey::Variability.as_str());
}
if custom {
spec.add(FieldKey::Custom, Value::Bool(true));
} else {
spec.remove(FieldKey::Custom.as_str());
}
Ok(spec
.as_attr_mut()
.expect("type guaranteed by require_spec_type_or_absent"))
}
pub fn create_relationship(
&mut self,
path: impl Into<Path>,
variability: Variability,
custom: bool,
) -> Result<RelationshipSpecMut<'_>, AuthoringError> {
let path: Path = path.into();
let (prim_path, property_name) = split_property_path(&path)?;
let data = self.writable_data_mut()?;
require_spec_type_or_absent(data, &path, SpecType::Relationship)?;
validate_token_vec(data, &prim_path, ChildrenKey::PropertyChildren)?;
ensure_prim_chain(data, &prim_path)?;
add_to_token_vec(
data.spec_mut(&prim_path).expect("ensure_prim_chain created it"),
&prim_path,
ChildrenKey::PropertyChildren,
&property_name,
)?;
if !data.has_spec(&path) {
data.create_spec(path.clone(), SpecType::Relationship);
}
let spec = data.spec_mut(&path).expect("just ensured");
if variability != Variability::Varying {
spec.add(FieldKey::Variability, Value::Variability(variability));
} else {
spec.remove(FieldKey::Variability.as_str());
}
if custom {
spec.add(FieldKey::Custom, Value::Bool(true));
} else {
spec.remove(FieldKey::Custom.as_str());
}
Ok(spec
.as_relationship_mut()
.expect("type guaranteed by require_spec_type_or_absent"))
}
pub fn prim(&self, path: impl Into<Path>) -> Option<PrimSpec<'_>> {
let path: Path = path.into();
self.data.as_data()?.spec(&path)?.as_prim()
}
pub fn prim_mut(&mut self, path: impl Into<Path>) -> Option<PrimSpecMut<'_>> {
let path: Path = path.into();
self.data.as_data_mut()?.spec_mut(&path)?.as_prim_mut()
}
pub fn attribute(&self, path: impl Into<Path>) -> Option<AttributeSpec<'_>> {
let path: Path = path.into();
self.data.as_data()?.spec(&path)?.as_attr()
}
pub fn attribute_mut(&mut self, path: impl Into<Path>) -> Option<AttributeSpecMut<'_>> {
let path: Path = path.into();
self.data.as_data_mut()?.spec_mut(&path)?.as_attr_mut()
}
pub fn relationship(&self, path: impl Into<Path>) -> Option<RelationshipSpec<'_>> {
let path: Path = path.into();
self.data.as_data()?.spec(&path)?.as_relationship()
}
pub fn relationship_mut(&mut self, path: impl Into<Path>) -> Option<RelationshipSpecMut<'_>> {
let path: Path = path.into();
self.data.as_data_mut()?.spec_mut(&path)?.as_relationship_mut()
}
pub fn pseudo_root(&self) -> Option<PseudoRootSpec<'_>> {
self.data.as_data()?.spec(&Path::abs_root())?.as_pseudo_root()
}
pub fn set_default_prim(&mut self, name: impl Into<String>) -> Result<(), AuthoringError> {
let name = name.into();
if name.is_empty() || name.starts_with('/') || Path::new(&format!("/{name}")).is_err() {
return Err(AuthoringError::InvalidPath {
path: Path::abs_root(),
reason: "defaultPrim must be a relative prim identifier or nested prim path",
});
}
self.pseudo_root_mut()?.set_default_prim(name);
Ok(())
}
pub fn missing_prim_ancestors(&self, target: &Path) -> Vec<Path> {
match target.parent() {
Some(p) => self.missing_prim_chain_inclusive(&p),
None => Vec::new(),
}
}
pub fn missing_prim_chain_inclusive(&self, target: &Path) -> Vec<Path> {
namespace_chain(target)
.map(|chain| {
chain
.into_iter()
.map(|elem| elem.path)
.filter(|p| !self.data.has_spec(p))
.collect()
})
.unwrap_or_default()
}
pub fn clear_default_prim(&mut self) -> Result<(), AuthoringError> {
self.clear_root_field(FieldKey::DefaultPrim)
}
pub fn relocates(&self) -> RelocateList {
self.data
.try_get(&Path::abs_root(), FieldKey::LayerRelocates.as_str())
.ok()
.flatten()
.and_then(|value| value.into_owned().try_as_relocates())
.unwrap_or_default()
}
pub fn has_relocates(&self) -> bool {
self.data
.has_field(&Path::abs_root(), FieldKey::LayerRelocates.as_str())
}
pub fn set_relocates(&mut self, relocates: RelocateList) -> Result<(), AuthoringError> {
self.pseudo_root_mut()?.set_relocates(relocates);
Ok(())
}
pub fn clear_relocates(&mut self) -> Result<(), AuthoringError> {
self.clear_root_field(FieldKey::LayerRelocates)
}
fn clear_root_field(&mut self, key: FieldKey) -> Result<(), AuthoringError> {
let data = self.writable_data_mut()?;
if let Some(spec) = data.spec_mut(&Path::abs_root()) {
spec.remove(key.as_str());
}
Ok(())
}
pub fn pseudo_root_mut(&mut self) -> Result<PseudoRootSpecMut<'_>, AuthoringError> {
let data = self.writable_data_mut()?;
let root = Path::abs_root();
match data.spec_type(&root) {
Some(SpecType::PseudoRoot) => {}
Some(_) => {
return Err(AuthoringError::InvalidPath {
path: root,
reason: "root spec exists with non-PseudoRoot SpecType",
})
}
None => {
data.create_spec(root.clone(), SpecType::PseudoRoot);
}
}
Ok(data
.spec_mut(&root)
.expect("just ensured")
.as_pseudo_root_mut()
.expect("type guaranteed above"))
}
pub(crate) fn writable_data_mut(&mut self) -> Result<&mut Data, AuthoringError> {
let identifier = self.identifier.clone();
self.data.as_data_mut().ok_or(AuthoringError::ReadOnly { identifier })
}
}
impl std::fmt::Debug for Layer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Layer")
.field("identifier", &self.identifier)
.finish_non_exhaustive()
}
}
impl AbstractData for Layer {
fn has_spec(&self, path: &Path) -> bool {
self.data.has_spec(path)
}
fn has_field(&self, path: &Path, field: &str) -> bool {
self.data.has_field(path, field)
}
fn spec_type(&self, path: &Path) -> Option<SpecType> {
self.data.spec_type(path)
}
fn try_get(&self, path: &Path, field: &str) -> Result<Option<std::borrow::Cow<'_, Value>>> {
self.data.try_get(path, field)
}
fn list(&self, path: &Path) -> Option<Vec<String>> {
self.data.list(path)
}
fn paths(&self) -> Vec<Path> {
self.data.paths()
}
fn as_data(&self) -> Option<&Data> {
self.data.as_data()
}
fn as_data_mut(&mut self) -> Option<&mut Data> {
None
}
}
fn ensure_prim_chain(data: &mut Data, target: &Path) -> Result<(), AuthoringError> {
let chain = namespace_chain(target)?;
let abs_root = Path::abs_root();
if let Some(existing) = data.spec_type(&abs_root) {
if existing != SpecType::PseudoRoot {
return Err(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(AuthoringError::InvalidPath {
path: elem.path.clone(),
reason: "spec exists with an incompatible SpecType",
});
}
}
validate_token_vec(data, parent_of(i), elem.child_key)?;
}
if data.spec_type(&abs_root).is_none() {
data.create_spec(abs_root.clone(), SpecType::PseudoRoot);
}
for (i, elem) in chain.iter().enumerate() {
let parent = parent_of(i);
let parent_spec = data.spec_mut(parent).expect("parent created by an earlier element");
add_to_token_vec(parent_spec, parent, elem.child_key, &elem.child_name)?;
if data.spec_type(&elem.path).is_none() {
let spec = data.create_spec(elem.path.clone(), elem.spec_type);
if elem.spec_type == SpecType::Prim {
spec.add(FieldKey::Specifier, Value::Specifier(Specifier::Over));
}
}
}
Ok(())
}
struct ChainElement {
path: Path,
spec_type: SpecType,
child_key: ChildrenKey,
child_name: String,
}
fn parse_prim_path(target: &Path, mut emit: impl FnMut(PathComponent<'_>)) -> Result<(), AuthoringError> {
let invalid = |reason: &'static str| 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 {
PathComponent::Prim(name) => {
if !Path::is_valid_identifier(name) {
return Err(invalid("prim path component is not a USD identifier"));
}
}
PathComponent::Variant { set, selection } => {
if !Path::is_valid_identifier(set) {
return Err(invalid("variant set name is not a USD identifier"));
}
if selection.is_empty() || !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: &Path) -> Result<Vec<ChainElement>, AuthoringError> {
let mut elems = Vec::new();
let mut cursor = Path::abs_root();
parse_prim_path(target, |component| match component {
PathComponent::Prim(name) => {
let path = cursor.append_path(name).expect("name validated as an identifier");
elems.push(ChainElement {
path: path.clone(),
spec_type: SpecType::Prim,
child_key: ChildrenKey::PrimChildren,
child_name: name.to_owned(),
});
cursor = path;
}
PathComponent::Variant { set, selection } => {
elems.push(ChainElement {
path: cursor.append_variant_selection(set, ""),
spec_type: SpecType::VariantSet,
child_key: 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: SpecType::Variant,
child_key: ChildrenKey::VariantChildren,
child_name: selection.to_owned(),
});
cursor = variant_path;
}
})?;
Ok(elems)
}
fn add_to_token_vec(spec: &mut Spec, owner_path: &Path, key: ChildrenKey, name: &str) -> Result<(), AuthoringError> {
match spec.get_mut(key.as_str()) {
Some(Value::TokenVec(v)) => {
if !v.iter().any(|n| n == name) {
v.push(name.to_owned());
}
}
Some(_) => {
return Err(AuthoringError::InvalidPath {
path: owner_path.clone(),
reason: "child-list field exists with non-TokenVec value",
});
}
None => {
spec.add(key, Value::TokenVec(vec![name.to_owned()]));
}
}
Ok(())
}
fn validate_token_vec(data: &Data, path: &Path, key: ChildrenKey) -> Result<(), AuthoringError> {
match data.spec(path).and_then(|spec| spec.get(key.as_str())) {
Some(Value::TokenVec(_)) | None => Ok(()),
Some(_) => Err(AuthoringError::InvalidPath {
path: path.clone(),
reason: "child-list field exists with non-TokenVec value",
}),
}
}
fn require_spec_type_or_absent(data: &Data, path: &Path, expected: SpecType) -> Result<(), AuthoringError> {
match data.spec_type(path) {
Some(existing) if existing != expected => Err(AuthoringError::InvalidPath {
path: path.clone(),
reason: "spec exists with the wrong SpecType",
}),
_ => Ok(()),
}
}
fn require_prim_path(path: &Path) -> Result<(), AuthoringError> {
parse_prim_path(path, |_| {})
}
fn require_prim_leaf(path: &Path) -> Result<(), AuthoringError> {
if path.is_prim_variant_selection_path() {
return Err(AuthoringError::InvalidPath {
path: path.clone(),
reason: "expected a prim path, but the leaf is a variant selection",
});
}
Ok(())
}
fn split_property_path(path: &Path) -> Result<(Path, String), AuthoringError> {
let (prim_path, suffix) = path.split_property().ok_or(AuthoringError::InvalidPath {
path: path.clone(),
reason: "expected property path",
})?;
require_prim_path(&prim_path)?;
if !suffix.split(':').all(Path::is_valid_identifier) {
return Err(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::*;
#[test]
fn relocates_round_trip() {
let mut layer = Layer::new_anonymous("test.usda");
assert!(!layer.has_relocates());
assert!(layer.relocates().is_empty());
let pairs = vec![
(Path::from("/Rig/Model"), Path::from("/Group/Model")),
(Path::from("/Rig/Dead"), Path::from("")),
];
layer.set_relocates(pairs.clone()).expect("in-memory layer is writable");
assert!(layer.has_relocates());
assert_eq!(layer.relocates(), pairs);
}
#[test]
fn empty_opinion_vs_cleared() {
let mut layer = Layer::new_anonymous("test.usda");
layer.set_relocates(RelocateList::new()).expect("writable");
assert!(layer.has_relocates());
assert!(layer.relocates().is_empty());
layer.clear_relocates().expect("writable");
assert!(!layer.has_relocates());
assert!(layer.relocates().is_empty());
}
}