diff --git a/crates/expr/src/host.rs b/crates/expr/src/host.rs index 43400ea1d..de2afd2ac 100644 --- a/crates/expr/src/host.rs +++ b/crates/expr/src/host.rs @@ -9,7 +9,7 @@ use effectcraft_geom::{Mat3, Mat4}; use effectcraft_keyframe::Value; -use effectcraft_project::{Comp, ItemId, ItemKind, Layer, LayerId, LayerSource, Node, PropGroup, Property}; +use effectcraft_project::{Comp, GroupKind, ItemId, ItemKind, Layer, LayerId, LayerSource, Node, PropGroup, Property}; use effectcraft_render::EvalCtx; use effectcraft_time::Tick; @@ -283,6 +283,9 @@ fn find_child<'g>(g: &'g PropGroup, key: &Key) -> Option<&'g Node> { .find(|n| n.match_id() == m) .or_else(|| g.children.iter().find(|n| n.name().eq_ignore_ascii_case(name))) .or_else(|| g.children.iter().find(|n| compact(n.name()) == c || compact(n.match_id()) == c)) + // An effect by its After Effects match name, an imported placeholder's effect id + // (`effect("Pseudo/0.25")`; its match id is one path segment, `Pseudo_0_25`). + .or_else(|| g.children.iter().find(|n| matches!(n.as_group().map(|g| &g.kind), Some(GroupKind::Effect { effect }) if effect == name))) } } } diff --git a/crates/expr/src/tests.rs b/crates/expr/src/tests.rs index f6d07bedc..96db71a6b 100644 --- a/crates/expr/src/tests.rs +++ b/crates/expr/src/tests.rs @@ -1377,12 +1377,13 @@ fn checkbox_and_dropdown_results() { } /// An effect EffectCraft does not have (an imported After Effects placeholder, #3087) is read by -/// name like any other: its parameters by their names, a popup 1-based. +/// name like any other: its parameters by their names, a popup 1-based. It is also found by its +/// After Effects match name, its effect id (the group's match id is one path segment). #[test] fn an_unknown_effects_parameters_are_read_by_name() { let mut f = fx(); let g = group_push(&mut f.p, |ids| { - let mut g = ids.group("Pseudo/0.25", "Settings"); + let mut g = ids.group("Pseudo_0_25", "Settings"); g.kind = effectcraft_project::GroupKind::Effect { effect: "Pseudo/0.25".into() }; g.children.push(ids.prop("param1", "Speed", Value::Scalar(3.0)).into()); g.children.push(ids.prop("param2", "Mode", Value::Enum(1)).into()); @@ -1392,6 +1393,8 @@ fn an_unknown_effects_parameters_are_read_by_name() { assert_close!(f.ev(f.b, ROT, "effect('Settings')('Speed') * 2", 0.0).unwrap().as_f64(), 6.0); assert_close!(f.ev(f.b, ROT, "effect('Settings')('Mode') * 10", 0.0).unwrap().as_f64(), 20.0); assert_close!(f.ev(f.b, ROT, "effect('Settings')(1)", 0.0).unwrap().as_f64(), 3.0); + assert_close!(f.ev(f.b, ROT, "effect('Pseudo/0.25')('Speed')", 0.0).unwrap().as_f64(), 3.0); + assert_close!(f.ev(f.b, ROT, "thisLayer('Effects')('Pseudo/0.25')('Speed')", 0.0).unwrap().as_f64(), 3.0); } #[test] diff --git a/docs/aep-import.md b/docs/aep-import.md index 9d44006b2..08efa6123 100644 --- a/docs/aep-import.md +++ b/docs/aep-import.md @@ -320,7 +320,8 @@ parameter gets the file's value: is imported and exported, and so is an expression on a stored effect parameter (its text and its switch). - **Effects without a counterpart** (third-party plug-ins, pseudo effects of expression controls, `Pseudo/…`) arrive as **placeholders** (#3087): an effect group whose effect id is the After - Effects match name. Like a missing plug-in it renders nothing and is listed by Find Missing + Effects match name (expressions also find it by that name, `effect("Pseudo/0.25")`; its match id + is that name as one path segment, `Pseudo_0_25`). Like a missing plug-in it renders nothing and is listed by Find Missing Effects. It holds the effect's parameters flat (without the twirl-down group markers), named as in After Effects, each with its value, keys and expression, in the units After Effects' expressions read (sliders and angles as stored, points in layer pixels, colours, checkboxes,