mod reader;
mod writer;
pub use reader::Archive;
pub use writer::ArchiveWriter;
use std::io::Cursor;
use anyhow::{bail, Context, Result};
use crate::{ar, sdf, tf, usdc};
pub struct UsdzFileFormat;
const USDZ_LAYER_NAME: &str = "layer.usdc";
impl sdf::FileFormat for UsdzFileFormat {
fn format_id(&self) -> tf::Token {
tf::Token::new("usdz")
}
fn extensions(&self) -> &[&str] {
&["usdz"]
}
fn caps(&self) -> sdf::FileFormatCaps {
sdf::FileFormatCaps::READ | sdf::FileFormatCaps::WRITE
}
fn resolve_layer(&self, resolver: &dyn ar::Resolver, resolved: &ar::ResolvedPath) -> Option<ar::ResolvedPath> {
let package = resolved.to_string_lossy();
if ar::is_package_relative_path(&package) {
return Some(resolved.clone());
}
match Archive::from_asset(resolver, resolved)
.ok()
.and_then(|a| a.first_layer_name())
{
Some(first) => Some(ar::ResolvedPath::new(ar::join_package_relative_path(&package, &first))),
None => Some(resolved.clone()),
}
}
fn read(&self, resolver: &dyn ar::Resolver, resolved: &ar::ResolvedPath) -> Result<sdf::LayerData> {
let s = resolved.to_string();
if let Some((_, inner)) = ar::split_package_relative_path_outer(&s) {
bail!("Nested USDZ files are not yet supported: '{inner}'");
}
Archive::from_asset(resolver, resolved)?
.read_first_layer()
.context("failed to read first layer from USDZ archive")
}
fn write(&self, data: &dyn sdf::AbstractData, sink: &mut dyn sdf::WriteSeek) -> Result<()> {
let mut buf = Vec::new();
usdc::CrateWriter::write(data, &mut Cursor::new(&mut buf))?;
let mut archive = ArchiveWriter::new(sink);
archive.add_layer(USDZ_LAYER_NAME, &buf)?;
archive.finish()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::usd::{Stage, TimeCode};
#[test]
fn resolves_packaged_reference() -> Result<()> {
let root =
b"#usda 1.0\n(defaultPrim = \"World\")\ndef \"World\" (prepend references = @./inner.usda@</Inner>) {}\n";
let inner = b"#usda 1.0\ndef \"Inner\" { custom int probe = 42 }\n";
let dir = tempfile::tempdir()?;
let path = dir.path().join("pkg.usdz");
let mut writer = ArchiveWriter::create(&path)?;
writer.add_layer("root.usda", root)?; writer.add_layer("inner.usda", inner)?;
writer.finish()?;
let stage = Stage::open(path.to_str().unwrap())?;
assert_eq!(
stage
.attribute("/World.probe")
.get_at::<sdf::Value>(TimeCode::new(0.0))?,
Some(sdf::Value::Int(42)),
"reference to a layer inside the package should compose"
);
Ok(())
}
}