Merge pull request #1558 from AyberkA0/script-keys-closed-comp

fix(script): per-key methods act on keys outside the active comp (#1531)
This commit is contained in:
BFlat authored and GitHub committed 2026-10-16 19:00:16 +07:00
commit 5c02fe902f
2 files changed
+46 -6

No files matched your search

+16 -6
View File
@@ -157,11 +157,16 @@ pub(crate) fn exec(s: &mut Session, id: &str, params: J) -> Result<J, String> {
s.state.selected_layers.clear();
}
let r = s.execute_for_script(id, params);
if let Some(c) = target
&& prev != Some(c)
&& s.state.active_comp == Some(c)
&& id != "comp.open"
{
if let Some(c) = target {
leave_comp(s, id, c, prev);
}
r.map_err(|e| e.to_string())
}
/// After a script's command on comp `c`, make `prev` (the comp active before it) active again,
/// unless the command opened `c` on purpose; with no comp to go back to, `c` stays open.
fn leave_comp(s: &mut Session, id: &str, c: effectcraft_engine::project::ItemId, prev: Option<effectcraft_engine::project::ItemId>) {
if prev != Some(c) && s.state.active_comp == Some(c) && id != "comp.open" {
match prev {
Some(p) if s.project.comp(p).is_some() => s.activate_comp(p),
_ => {
@@ -171,7 +176,6 @@ pub(crate) fn exec(s: &mut Session, id: &str, params: J) -> Result<J, String> {
}
}
}
r.map_err(|e| e.to_string())
}
/// Run a keyframe command that acts on the selected keys (`keys.velocity`, `keys.interpolation`,
@@ -195,11 +199,17 @@ fn exec_on_keys(s: &mut Session, id: &str, params: J, targets: &J) -> Result<J,
let key = l.props.find(prop).and_then(|pr| pr.keys.get(usize::try_from(index).ok()?)).ok_or("the keyframe no longer exists")?;
keys.push(KeyRef { layer: LayerId(layer), prop, time: key.time });
}
// Make the keys' comp active before selecting them: activating a comp brings back the key
// selection it had, which would replace these keys and leave the command nothing to act on
// when the comp isn't the active one (#1531).
let prev = s.state.active_comp;
s.activate_comp(cid);
let saved = std::mem::replace(&mut s.state.selected_keys, keys);
let r = exec(s, id, params);
let comp = s.active_comp();
let exists = |k: &KeyRef| comp.and_then(|c| c.layer(k.layer)).and_then(|l| l.props.find(k.prop)).is_some_and(|pr| pr.keys.iter().any(|x| x.time == k.time));
s.state.selected_keys = saved.into_iter().filter(exists).collect();
leave_comp(s, id, cid, prev);
r
}
+30
View File
@@ -1270,6 +1270,36 @@ fn effect_parameter_interpolation_and_ease() {
assert_eq!(o.result, json!([true, true, 16.67, 33.33, true]));
}
/// #1531: the per-key methods act on a comp that isn't the active one too. Activating the keys'
/// comp brought back its own (empty) key selection over the keys named, so the interpolation and
/// the ease were silently dropped once another comp with a layer had been made first.
#[test]
fn key_interpolation_and_ease_apply_outside_the_active_comp() {
let mut s = session();
let o = ok(
&mut s,
r#"
var a = app.project.items.addComp("A", 400, 400, 1, 10, 20); a.layers.addNull();
var c = app.project.items.addComp("C", 400, 400, 1, 10, 20);
var n = c.layers.addNull(); n.threeDLayer = true;
var p = n.property("ADBE Transform Group").property("ADBE Rotate Y");
p.setValueAtTime(1, 0); p.setValueAtTime(2, -178);
for (var j = 1; j <= 2; j++) {
p.setInterpolationTypeAtKey(j, KeyframeInterpolationType.BEZIER, KeyframeInterpolationType.BEZIER);
p.setTemporalEaseAtKey(j, [new KeyframeEase(0, 60)], [new KeyframeEase(0, 60)]);
}
[p.keyOutInterpolationType(1) == KeyframeInterpolationType.BEZIER,
p.keyInInterpolationType(2) == KeyframeInterpolationType.BEZIER,
p.keyOutTemporalEase(1)[0].influence,
Math.round(p.valueAtTime(1.25, false) * 100) / 100]
"#,
);
let v = o.result;
assert_eq!((&v[0], &v[1], &v[2]), (&json!(true), &json!(true), &json!(60)), "{v}");
// Eased, not the linear -44.5.
assert!(v[3].as_f64().is_some_and(|x| (x - -44.5).abs() > 1.0), "{v}");
}
/// #808: sourcePointToComp and compPointToSource follow the layer's transform.
#[test]
fn source_point_conversions_follow_the_layer_transform() {