#3087: expressions find an effect by its After Effects match name

effect("Pseudo/0.25") and thisLayer("Effects")("Pseudo/0.25") now match a
placeholder's effect id, now that its match id is one path segment.
This commit is contained in:
arnav-hiwarkar committed 2026-11-12 13:06:51 +05:30
1 parent 0da7691d9d
commit 6f8c39d5f4
3 files changed
+11 -4

No files matched your search

+4 -1
View File
@@ -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)))
}
}
}
+5 -2
View File
@@ -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]
+2 -1
View File
@@ -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,