use std::cmp::Ordering;
use std::{collections::HashMap, fmt::Debug, mem};
use bytemuck::{Pod, Zeroable};
use strum::FromRepr;
use crate::tf::Token;
mod asset_path;
mod change;
mod coerce;
mod copy;
mod data;
pub mod expr;
mod file_format;
mod layer;
pub(crate) mod layer_registry;
mod ordering;
mod path;
pub mod path_expr;
mod path_table;
pub mod schema;
pub mod sink;
mod spec;
mod value;
mod value_type;
pub use asset_path::AssetPath;
pub(crate) use asset_path::{
AssetExpressionFailure, AssetOutcome, evaluate_asset_paths, holds_asset_expression, resolve_asset_paths,
};
pub use change::{ChangeEntry, ChangeFlags, ChangeList, FieldChange};
pub use copy::{
CopyChildren, CopyChildrenArgs, CopyValue, CopyValueArgs, copy_spec, copy_spec_with, copy_spec_within,
should_copy_children, should_copy_value,
};
pub(crate) use copy::{author_spec, is_children_field};
pub use data::{AbstractData, CowData, Data, DataError, Patch};
pub use expr::{Evaluation, EvaluationValue, Expr, ExprError, StringEvaluation, StringSegment};
pub use file_format::{FileFormat, FileFormatCaps, FormatError, WriteSeek};
pub use layer::{
AuthoringError, EditError, ExportError, Layer, LayerEdit, LayerSink, LayerSinkId, PendingLayerChange,
default_prim_path,
};
pub(crate) use layer::{dry_run_layers, edit_layers};
pub use layer_registry::LayerRegistry;
pub(crate) use layer_registry::LoadError;
pub use ordering::{apply_ordering, element_cmp};
pub use path::{IntoPath, Path, PathComponent, PathComponents, PathElement, PathParseError, path, try_into_path};
pub use path_expr::{EvalError, ExpressionReference, PathExpression, PathPattern, PredicateExpression};
pub use path_table::PathTable;
pub use schema::{ChildrenKey, FieldKey, folds_list_ops};
pub use spec::{
AttributeSpec, AttributeSpecMut, AttributeSpecRef, PrimSpec, PrimSpecMut, PrimSpecRef, PropertySpec,
PropertySpecMut, PropertySpecRef, PseudoRootSpec, PseudoRootSpecMut, PseudoRootSpecRef, RelationshipSpec,
RelationshipSpecMut, RelationshipSpecRef, Spec, SpecData, SpecError, SpecMut, SpecRef, SpecType,
};
pub use value::{CastError, FromValueCast, Value, ValueKind, dictionary_over};
pub use value_type::{Dimensions, Role, ValueTypeError, ValueTypeName};
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum Specifier {
Def,
Over,
Class,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum Permission {
Public,
Private,
}
#[repr(i32)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum Variability {
#[default]
Varying,
Uniform,
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, derive_more::From)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TimeCode(pub f64);
impl TimeCode {
#[inline]
pub fn value(self) -> f64 {
self.0
}
}
impl TryFrom<Value> for TimeCode {
type Error = CastError;
fn try_from(value: Value) -> Result<Self, Self::Error> {
match value {
Value::TimeCode(v) => Ok(v),
other => Err(CastError::TypeMismatch {
target: "TimeCode",
actual: (&other).into(),
}),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LayerOffset {
pub offset: f64,
pub scale: f64,
}
impl Default for LayerOffset {
fn default() -> Self {
Self {
offset: 0.0,
scale: 1.0,
}
}
}
impl LayerOffset {
pub const IDENTITY: LayerOffset = LayerOffset {
offset: 0.0,
scale: 1.0,
};
#[inline]
pub fn new(offset: f64, scale: f64) -> Self {
Self { offset, scale }
}
#[inline]
pub fn scale_only(scale: f64) -> Self {
Self { offset: 0.0, scale }
}
#[inline]
pub fn is_valid(&self) -> bool {
self.offset.is_finite() && self.scale.is_finite()
}
#[inline]
pub fn is_identity(&self) -> bool {
self.offset == 0.0 && self.scale == 1.0
}
#[inline]
pub fn to_bits(&self) -> (u64, u64) {
(self.offset.to_bits(), self.scale.to_bits())
}
#[inline]
pub fn apply(&self, time: f64) -> f64 {
self.offset + self.scale * time
}
pub fn apply_to_value(&self, value: &mut Value) {
if self.is_identity() {
return;
}
match value {
Value::TimeCode(time) => *time = TimeCode(self.apply(time.value())),
Value::TimeCodeVec(times) => {
for time in times {
*time = TimeCode(self.apply(time.value()));
}
}
Value::TimeSamples(samples) => self.apply_to_samples(samples),
Value::Dictionary(entries) => {
for entry in entries.values_mut() {
self.apply_to_value(entry);
}
}
Value::ValueVec(values) => {
for value in values {
self.apply_to_value(value);
}
}
_ => {}
}
}
pub fn apply_to_samples(&self, samples: &mut TimeSampleMap) {
for (time, sample) in samples.iter_mut() {
*time = self.apply(*time);
self.apply_to_value(sample);
}
if self.scale < 0.0 {
samples.reverse();
}
}
pub fn sample_in_stage_time(
&self,
samples: &TimeSampleMap,
time: f64,
interp: impl Fn(&TimeSampleMap, f64) -> Option<Value>,
) -> Option<Value> {
let mut value = interp(samples, self.inverse().apply(time))?;
self.apply_to_value(&mut value);
Some(value)
}
#[inline]
pub fn inverse(&self) -> LayerOffset {
if self.scale == 0.0 {
return LayerOffset::IDENTITY;
}
LayerOffset {
offset: -self.offset / self.scale,
scale: 1.0 / self.scale,
}
}
#[inline]
pub fn is_valid_composition(&self) -> bool {
self.offset.is_finite() && self.scale.is_finite() && self.scale > 0.0
}
#[inline]
pub fn sanitized(self) -> Self {
if self.is_valid_composition() {
self
} else {
Self::IDENTITY
}
}
#[inline]
pub fn concatenate(&self, inner: &LayerOffset) -> LayerOffset {
LayerOffset {
offset: self.offset + self.scale * inner.offset,
scale: self.scale * inner.scale,
}
}
}
#[derive(Debug, Default, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Payload {
#[cfg_attr(feature = "serde", serde(rename = "asset", skip_serializing_if = "String::is_empty"))]
pub asset_path: String,
#[cfg_attr(feature = "serde", serde(rename = "path", skip_serializing_if = "Path::is_empty"))]
pub prim_path: Path,
#[cfg_attr(
feature = "serde",
serde(rename = "layerOffset", skip_serializing_if = "Option::is_none")
)]
pub layer_offset: Option<LayerOffset>,
}
#[derive(Debug, Default, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Reference {
#[cfg_attr(feature = "serde", serde(rename = "asset", skip_serializing_if = "String::is_empty"))]
pub asset_path: String,
#[cfg_attr(feature = "serde", serde(rename = "path", skip_serializing_if = "Path::is_empty"))]
pub prim_path: Path,
#[cfg_attr(feature = "serde", serde(rename = "layerOffset"))]
pub layer_offset: LayerOffset,
#[cfg_attr(
feature = "serde",
serde(rename = "customData", skip_serializing_if = "HashMap::is_empty")
)]
pub custom_data: HashMap<String, Value>,
}
mod list_op;
pub use list_op::ListOp;
pub type Dictionary = std::collections::HashMap<String, Value>;
pub type IntListOp = ListOp<i32>;
pub type UintListOp = ListOp<u32>;
pub type Int64ListOp = ListOp<i64>;
pub type Uint64ListOp = ListOp<u64>;
pub type StringListOp = ListOp<String>;
pub type TokenListOp = ListOp<Token>;
pub type PathListOp = ListOp<Path>;
pub type ReferenceListOp = ListOp<Reference>;
pub type PayloadListOp = ListOp<Payload>;
pub type TimeSampleMap = Vec<(f64, Value)>;
#[inline]
pub fn compare_sample_times(a: f64, b: f64) -> Ordering {
let zero_folded = |t: f64| if t == 0.0 { 0.0 } else { t };
zero_folded(a).total_cmp(&zero_folded(b))
}
pub fn normalize_time_samples(mut samples: Vec<(f64, Value)>) -> TimeSampleMap {
if samples.is_sorted_by(|a, b| compare_sample_times(a.0, b.0).is_lt()) {
return samples;
}
samples.sort_by(|a, b| compare_sample_times(a.0, b.0));
samples.dedup_by(|later, kept| {
if compare_sample_times(kept.0, later.0).is_ne() {
return false;
}
mem::swap(&mut kept.1, &mut later.1);
true
});
samples
}
pub type Relocate = (Path, Path);
pub type RelocateList = Vec<Relocate>;
pub type LayerData = Box<dyn AbstractData>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_time_samples_order() {
let samples = normalize_time_samples(vec![
(10.0, Value::Int(1)),
(-0.0, Value::Int(2)),
(0.0, Value::Int(3)),
(10.0, Value::Int(4)),
(f64::NAN, Value::Int(5)),
]);
let times: Vec<f64> = samples.iter().map(|(t, _)| *t).collect();
assert_eq!(
times[0].to_bits(),
(-0.0_f64).to_bits(),
"the time keeps its first spelling"
);
assert_eq!(times[1], 10.0);
assert!(times[2].is_nan(), "a positive NaN orders past every number");
let values: Vec<&Value> = samples.iter().map(|(_, v)| v).collect();
assert_eq!(
values,
[&Value::Int(3), &Value::Int(4), &Value::Int(5)],
"the last sample of a repeated time wins, the two zeros being one time"
);
assert_eq!(
normalize_time_samples(vec![(f64::NAN, Value::Int(1)), (-f64::NAN, Value::Int(2))]).len(),
2,
"the order separates the NaNs by sign, so they are two times"
);
assert_eq!(
normalize_time_samples(vec![(0.0, Value::Int(1)), (1.0, Value::Int(2))]),
vec![(0.0, Value::Int(1)), (1.0, Value::Int(2))]
);
}
#[test]
fn layer_offset_identity_is_identity() {
assert!(LayerOffset::IDENTITY.is_identity());
assert!(LayerOffset::default().is_identity());
assert!(!LayerOffset::new(0.0, 2.0).is_identity());
assert!(!LayerOffset::new(1.0, 1.0).is_identity());
}
#[test]
fn layer_offset_valid_composition_rejects_non_positive_scale() {
assert!(LayerOffset::new(10.0, 1.0).is_valid_composition());
assert!(!LayerOffset::new(10.0, 0.0).is_valid_composition());
assert!(!LayerOffset::new(10.0, -1.0).is_valid_composition());
assert!(!LayerOffset::new(f64::INFINITY, 1.0).is_valid_composition());
assert!(!LayerOffset::new(0.0, f64::NAN).is_valid_composition());
}
#[test]
fn samples_offset_scale() {
let mut samples: TimeSampleMap = vec![(1.0, Value::Double(0.0)), (5.0, Value::Double(1.0))];
LayerOffset::new(10.0, 2.0).apply_to_samples(&mut samples);
let times: Vec<f64> = samples.iter().map(|(t, _)| *t).collect();
assert_eq!(times, vec![12.0, 20.0]);
}
#[test]
fn samples_negative_scale() {
let mut samples: TimeSampleMap = vec![(1.0, Value::Double(0.0)), (5.0, Value::Double(1.0))];
LayerOffset::new(0.0, -1.0).apply_to_samples(&mut samples);
let times: Vec<f64> = samples.iter().map(|(t, _)| *t).collect();
assert_eq!(times, vec![-5.0, -1.0]);
assert_eq!(samples[0].1, Value::Double(1.0));
}
#[test]
fn value_identity_passthrough() {
let samples: TimeSampleMap = vec![(1.0, Value::Double(0.0))];
let mut value = Value::TimeSamples(samples.clone());
LayerOffset::IDENTITY.apply_to_value(&mut value);
assert_eq!(value, Value::TimeSamples(samples));
}
#[test]
fn value_time_codes() {
let offset = LayerOffset::new(10.0, 2.0);
let mut scalar = Value::TimeCode(TimeCode(5.0));
offset.apply_to_value(&mut scalar);
assert_eq!(scalar, Value::TimeCode(TimeCode(20.0)));
let mut array = Value::TimeCodeVec(vec![TimeCode(5.0), TimeCode(10.0)]);
offset.apply_to_value(&mut array);
assert_eq!(array, Value::TimeCodeVec(vec![TimeCode(20.0), TimeCode(30.0)]));
let mut plain = Value::Double(5.0);
offset.apply_to_value(&mut plain);
assert_eq!(plain, Value::Double(5.0));
}
#[test]
fn value_nested() {
let offset = LayerOffset::new(10.0, 2.0);
let inner = HashMap::from([("deep".to_string(), Value::TimeCode(TimeCode(5.0)))]);
let mut dict = Value::Dictionary(HashMap::from([
("nested".to_string(), Value::Dictionary(inner)),
("kept".to_string(), Value::Double(5.0)),
]));
assert!(dict.holds_time_codes());
offset.apply_to_value(&mut dict);
let entries = dict.try_as_dictionary_ref().expect("dictionary");
assert_eq!(entries.get("kept"), Some(&Value::Double(5.0)));
let nested = entries.get("nested").expect("nested").try_as_dictionary_ref().unwrap();
assert_eq!(nested.get("deep"), Some(&Value::TimeCode(TimeCode(20.0))));
let mut samples = Value::TimeSamples(vec![(1.0, Value::TimeCode(TimeCode(5.0)))]);
offset.apply_to_value(&mut samples);
assert_eq!(
samples,
Value::TimeSamples(vec![(12.0, Value::TimeCode(TimeCode(20.0)))])
);
}
#[test]
fn layer_offset_sanitized_drops_invalid_to_identity() {
assert_eq!(LayerOffset::new(10.0, 2.0).sanitized(), LayerOffset::new(10.0, 2.0));
assert_eq!(LayerOffset::new(5.0, -1.0).sanitized(), LayerOffset::IDENTITY);
assert_eq!(LayerOffset::new(5.0, 0.0).sanitized(), LayerOffset::IDENTITY);
}
#[test]
fn layer_offset_concatenate_matches_spec_formula() {
let outer = LayerOffset::new(10.0, 2.0);
let inner = LayerOffset::new(20.0, 1.0);
assert_eq!(outer.concatenate(&inner), LayerOffset::new(50.0, 2.0));
}
#[test]
fn layer_offset_concatenate_is_associative() {
let a = LayerOffset::new(10.0, 2.0);
let b = LayerOffset::new(20.0, 0.5);
let c = LayerOffset::new(5.0, 3.0);
let ab_c = a.concatenate(&b).concatenate(&c);
let a_bc = a.concatenate(&b.concatenate(&c));
assert!((ab_c.offset - a_bc.offset).abs() < 1e-12);
assert!((ab_c.scale - a_bc.scale).abs() < 1e-12);
}
#[test]
fn layer_offset_identity_is_neutral() {
let a = LayerOffset::new(10.0, 2.0);
assert_eq!(a.concatenate(&LayerOffset::IDENTITY), a);
assert_eq!(LayerOffset::IDENTITY.concatenate(&a), a);
}
#[test]
fn layer_offset_inverse_undoes_apply() {
let a = LayerOffset::new(10.0, 2.0);
assert_eq!(a.inverse(), LayerOffset::new(-5.0, 0.5));
assert_eq!(a.inverse().apply(a.apply(7.0)), 7.0);
let b = LayerOffset::new(1.0, 3.0);
assert!((b.inverse().apply(b.apply(7.0)) - 7.0).abs() < 1e-9);
assert_eq!(LayerOffset::IDENTITY.inverse(), LayerOffset::IDENTITY);
assert_eq!(LayerOffset::new(3.0, 0.0).inverse(), LayerOffset::IDENTITY);
}
}