use std::collections::HashSet;
use crate::sdf::schema::FieldKey;
use crate::sdf::{self, AttributeSpec, AttributeSpecMut, Path, Value, Variability};
use super::QueryError;
pub(crate) const GENERATED_MANIFEST_TAG: &str = "generated_manifest.usda";
pub(crate) const CLIP_MANIFEST_TAG: &str = "clip_manifest.usda";
pub(crate) fn generate_manifest(
clips: &[(&sdf::Layer, Option<f64>)],
clip_prim_path: &Path,
tag: &str,
) -> Result<sdf::Layer, QueryError> {
let mut manifest = sdf::Layer::new_anonymous(tag);
manifest.edit(|edit| {
let data = edit.data_mut();
let mut declared: Vec<Path> = Vec::new();
let mut walked: HashSet<&str> = HashSet::new();
for &(clip, _) in clips {
if !walked.insert(clip.identifier()) {
continue;
}
for (path, type_name, variability) in sampled_attributes(clip, clip_prim_path)? {
if data.has_spec(&path) {
continue;
}
if AttributeSpec::new(data, path.clone(), type_name, variability, false).is_ok() {
declared.push(path);
}
}
}
for &(clip, active) in clips {
let Some(time) = active else {
continue;
};
for path in declared.iter().filter(|path| !has_time_samples(clip, path)) {
AttributeSpecMut::get(data, path.clone())
.expect("declared above")
.set_time_sample(time, Value::ValueBlock)?;
}
}
Ok(())
})?;
Ok(manifest)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct ClipSetKey {
pub prim: Path,
pub clip_set: String,
}
fn sampled_attributes(
clip: &sdf::Layer,
clip_prim_path: &Path,
) -> Result<Vec<(Path, sdf::ValueTypeName, Variability)>, sdf::PathParseError> {
let mut out = Vec::new();
for path in clip.data().spec_paths() {
if !path.is_property_path() || !path.has_prefix(clip_prim_path) {
continue;
}
let Some(attr) = clip.attribute(&path)? else {
continue;
};
let Some(type_name) = attr.type_name() else {
continue;
};
let variability = attr.variability();
if has_time_samples(clip, &path) {
out.push((path, type_name, variability));
}
}
Ok(out)
}
fn has_time_samples(layer: &sdf::Layer, path: &Path) -> bool {
match layer.data().try_field(path, FieldKey::TimeSamples.as_str()) {
Ok(Some(field)) => matches!(&*field, Value::TimeSamples(samples) if !samples.is_empty()),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Result;
use crate::usda;
fn clip(name: &str, text: &str) -> sdf::Layer {
sdf::Layer::new(name, Box::new(usda::parse(text).expect("valid usda")))
}
fn declared(manifest: &sdf::Layer) -> Vec<String> {
manifest
.data()
.spec_paths()
.into_iter()
.filter(|path| path.is_property_path())
.map(|path| path.as_str().to_owned())
.collect()
}
#[test]
fn declares_sampled_union() -> Result<()> {
let one = clip(
"one.usda",
r#"#usda 1.0
def "Clip"
{
def "A"
{
double a.timeSamples = { 0: 1.0, 1: 2.0 }
double b.timeSamples = { 0: 3.0, 1: 4.0 }
double d
double e = 5.0
}
def "B"
{
double g.timeSamples = { 0: 1.0, 1: 2.0 }
}
}
"#,
);
let two = clip(
"two.usda",
r#"#usda 1.0
def "Clip"
{
def "A"
{
double z.timeSamples = { 0: 1.0 }
}
def "B"
{
double y.timeSamples = { 0: 1.0 }
}
}
"#,
);
let manifest = generate_manifest(&[(&one, None), (&two, None)], &sdf::path("/Clip/A")?, "test.usda")?;
assert_eq!(declared(&manifest), ["/Clip/A.a", "/Clip/A.b", "/Clip/A.z"]);
let a = manifest.attribute("/Clip/A.a")?.expect("declared");
assert_eq!(a.type_name(), Some(sdf::ValueTypeName::DOUBLE));
assert!(a.default().is_none());
assert!(a.time_samples().is_none());
Ok(())
}
#[test]
fn declares_variant_and_uniform() -> Result<()> {
let one = clip(
"one.usda",
r#"#usda 1.0
def "A"
{
uniform double u.timeSamples = { 0: 1.0 }
variantSet "v" = {
"a" {
double c.timeSamples = { 0: 5.0 }
}
}
}
"#,
);
let manifest = generate_manifest(&[(&one, None)], &sdf::path("/A")?, "test.usda")?;
assert_eq!(declared(&manifest), ["/A.u", "/A{v=a}.c"]);
assert_eq!(
manifest.attribute("/A.u")?.expect("declared").variability(),
Variability::Uniform
);
assert_eq!(
manifest.attribute("/A{v=a}.c")?.expect("declared").variability(),
Variability::Varying
);
Ok(())
}
#[test]
fn blocks_clips_without_samples() -> Result<()> {
let full = clip(
"full.usda",
r#"#usda 1.0
def "A"
{
double both.timeSamples = { 0: 1.0 }
double only.timeSamples = { 0: 2.0 }
}
"#,
);
let partial = clip(
"partial.usda",
r#"#usda 1.0
def "A"
{
double both.timeSamples = { 0: 3.0 }
}
"#,
);
let manifest = generate_manifest(
&[(&full, Some(0.0)), (&partial, Some(4.0)), (&partial, Some(8.0))],
&sdf::path("/A")?,
"test.usda",
)?;
assert!(
manifest
.attribute("/A.both")?
.expect("declared")
.time_samples()
.is_none()
);
let only = manifest
.attribute("/A.only")?
.expect("declared")
.time_samples()
.expect("blocked");
assert_eq!(only, vec![(4.0, Value::ValueBlock), (8.0, Value::ValueBlock)]);
Ok(())
}
#[test]
fn no_blocks_without_active_times() -> Result<()> {
let empty = clip("empty.usda", "#usda 1.0\ndef \"A\"\n{\n}\n");
let sampled = clip(
"sampled.usda",
"#usda 1.0\ndef \"A\"\n{\n double x.timeSamples = { 0: 1.0 }\n}\n",
);
let manifest = generate_manifest(&[(&sampled, None), (&empty, None)], &sdf::path("/A")?, "test.usda")?;
assert!(manifest.attribute("/A.x")?.expect("declared").time_samples().is_none());
Ok(())
}
}