Expressions: a layer is a callable property group

In After Effects a layer is a property group: thisLayer("transform"),
thisComp.layer("Other")("effects")("Blur"), thisLayer(1). In
EffectCraft's expression runtime a Layer was a plain object, so those
failed with "TypeError: not a callable function", including the
references the pick whip writes with Compact English off
(thisComp.layer("Other")("effects")("ec.noise.fractal")(...)).

A Layer is now callable like a property group (built as __mkGroup
builds groups), looking up its own properties by match name, name or
index, and keeps its methods, name and index. The places that took a
layer for a plain object (the result reading as the layer's index,
thisComp.layer(layer, offset), a Layer Control's value handed through
the property proxy) accept it.

Test: layers_are_callable_property_groups (fails before).
This commit is contained in:
tylerjenningsw
2026-10-27 14:03:59 -04:00
parent dd12ed8f9c
commit 3f88c72a43
2 changed files with 33 additions and 5 deletions
+16 -5
View File
@@ -44,7 +44,7 @@ function __finish(r) {
function __result(r) {
r = __v(r);
if (r !== null && typeof r === 'object' && r.__isLayer) return r.index;
if (r !== null && r !== undefined && r.__isLayer) return r.index;
if (r !== null && typeof r === 'object' && r.__isTextStyle) return r.__out();
// `[temp, temp]` where temp is a property (pick-whip 1D → 2D): read the values here, while
// host requests can still be answered and the script re-run.
@@ -464,7 +464,7 @@ Comp.prototype = {
return __err('Composition "' + this.name + '" has no active camera');
},
layer: function (a, rel) {
if (a !== null && typeof a === 'object' && a.__isLayer) return this.layer(a.index + (rel || 0));
if (a !== null && a !== undefined && a.__isLayer) return this.layer(a.index + (rel || 0));
var id = __h(__LAYER, this.__c, a);
if (id === null) __err(typeof a === 'string' ? 'Layer named "' + a + '" does not exist' : 'Layer index ' + a + ' out of range');
return new Layer(this.__c, id);
@@ -542,7 +542,17 @@ var thisProject = {
// Layer -----------------------------------------------------------------------------------------
function Layer(c, l) { this.__c = c; this.__l = l; this.__i = null; }
// A layer is a property group too, as in After Effects: `thisLayer("transform")`, another layer's
// `("effects")("Slider Control")`, `thisLayer(1)` (what the pick whip writes with Compact English
// off). Callable like a group (`__mkGroup`), with the Layer methods.
function Layer(c, l) {
var f = function (k) { return f.__group(k); };
delete f.name;
delete f.length;
Object.setPrototypeOf(f, Layer.prototype);
f.__c = c; f.__l = l; f.__i = null;
return f;
}
function __m3(m, p) {
var x = p[0] || 0, y = p[1] || 0;
@@ -846,8 +856,9 @@ var __propHandler = {
var v = t.value;
if (v === null || v === undefined) return undefined;
var x = v[k];
// Property groups are callable proxies: hand them over as they are, not bound like methods (#809).
if (x?.__isGroup) return x;
// Property groups and layers are callable: hand them over as they are, not bound like methods
// (#809).
if (x?.__isGroup || x?.__isLayer) return x;
return typeof x === 'function' ? x.bind(v) : x;
},
};
+17
View File
@@ -1370,4 +1370,21 @@ fn time_to_frames_preserves_coercion_and_small_durations() {
}
}
/// A layer is a property group too, as in After Effects: `thisLayer("transform")`, another
/// layer's `("effects")("Slider Control")` or `(1)`, as the pick whip writes them with Compact
/// English off (they failed: "not a callable function"). It is still a layer.
#[test]
fn layers_are_callable_property_groups() {
let f = fx();
assert_close!(f.num("thisLayer(\"transform\")(\"opacity\")", 0.0), 100.0);
assert_close!(f.num("thisComp.layer(\"A\")(\"effects\")(\"ec.control.slider\")(\"slider\")", 0.0), 42.0);
assert_close!(f.num("thisComp.layer(\"A\")(\"effects\")(\"Slider Control\")(1)", 0.0), 42.0);
assert_close!(f.num("thisComp.layer(1)(\"effects\")(1)(1)", 0.0), 42.0);
// Its own name, index and methods, and a layer as the result still reads as its index.
assert_close!(f.num("thisLayer.name == \"A\" ? thisComp.layer(\"B\").index : 0", 0.0), 2.0);
assert_close!(f.num("thisComp.layer(\"B\")", 0.0), 2.0);
assert_close!(f.num("thisComp.layer(thisComp.layer(\"B\"), -1).index", 0.0), 1.0);
assert!(f.err("thisLayer(\"nope\")").contains("no property"), "{}", f.err("thisLayer(\"nope\")"));
}
mod property_group;