mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
#809: layer.source and a Layer Control's layer work in expressions
layer.source was undefined in expressions, and a layer from a Layer Control had no transform. Both now work. Co-Authored-By: Claude Haiku 5.5 <[email protected]>
This commit is contained in:
1 parent
470ea7a3af
commit
4d105f02d4
4 files changed
+79
-2
No files matched your search
@@ -63,6 +63,10 @@ pub enum Req {
|
||||
Sample { comp: u64, layer: u64, x: Secs, y: Secs, rx: Secs, ry: Secs, post: bool, t: Secs },
|
||||
/// `thisProject`: `[bitsPerChannel, linearBlending, workingSpace]`.
|
||||
Project,
|
||||
/// A layer's source item (AE's `Layer.source`): `[kind, id, name, width, height, duration,
|
||||
/// frameDuration, pixelAspect]`, kind `"comp"` (id = the precomp's composition) or `"footage"`
|
||||
/// (footage, solids and models), or null for layers without an item (text, shape, null…).
|
||||
SourceInfo { comp: u64, layer: u64 },
|
||||
}
|
||||
|
||||
/// Parse CSV/TSV text (RFC 4180 quoting) into rows of cells.
|
||||
@@ -457,6 +461,28 @@ impl<'a> Resolver<'a> {
|
||||
Resp::Bool(project.settings.blend_linear || project.settings.linearize),
|
||||
Resp::Str(project.settings.working_space.map(|w| format!("{w:?}")).unwrap_or_default()),
|
||||
]),
|
||||
Req::SourceInfo { comp, layer } => {
|
||||
let c = self.comp(*comp)?;
|
||||
let l = self.layer(*comp, *layer)?;
|
||||
let it = project.item(l.source.item()?)?;
|
||||
let (kind, aspect, frame, duration) = match &it.kind {
|
||||
ItemKind::Comp(nc) => ("comp", nc.pixel_aspect, nc.frame_duration().seconds(), nc.duration.seconds()),
|
||||
ItemKind::Footage(f) => ("footage", f.pixel_aspect, f.frame_rate.frame_duration().seconds(), f.duration.seconds()),
|
||||
ItemKind::Solid(s) => ("footage", s.pixel_aspect, c.frame_duration().seconds(), c.duration.seconds()),
|
||||
ItemKind::Folder => return None,
|
||||
};
|
||||
let (w, h) = it.dimensions().unwrap_or((0, 0));
|
||||
Resp::List(vec![
|
||||
Resp::Str(kind.into()),
|
||||
Resp::Num(it.id.0 as f64),
|
||||
Resp::Str(it.name.clone()),
|
||||
Resp::Num(w as f64),
|
||||
Resp::Num(h as f64),
|
||||
Resp::Num(duration),
|
||||
Resp::Num(frame),
|
||||
Resp::Num(aspect),
|
||||
])
|
||||
}
|
||||
Req::Sample { comp, layer, x, y, rx, ry, post, t } => {
|
||||
let l = self.layer(*comp, *layer)?;
|
||||
let f = |b: &Secs| f64::from_bits(*b);
|
||||
|
||||
@@ -18,7 +18,7 @@ var selectorValue = [100, 100, 100];
|
||||
|
||||
// Request ops (must match runtime.rs).
|
||||
var __COMP = 0, __COMPNAME = 1, __LAYER = 2, __LINFO = 3, __CHILD = 4, __PINFO = 5, __VALUE = 6, __XFORM = 7, __RECT = 8, __MARKERS = 9, __DOC = 10;
|
||||
var __FOOT = 11, __FDATA = 12, __SAMPLE = 13, __PROJ = 14;
|
||||
var __FOOT = 11, __FDATA = 12, __SAMPLE = 13, __PROJ = 14, __SOURCE = 15;
|
||||
|
||||
function __begin(t, v, c, l, path, uid, idx, fd, ti, tt, sv) {
|
||||
time = t;
|
||||
@@ -569,6 +569,14 @@ Layer.prototype = {
|
||||
get enabled() { return this.__info[11]; },
|
||||
get active() { return this.__info[11] && time >= this.inPoint && time < this.outPoint; },
|
||||
get containingComp() { return new Comp(this.__c); },
|
||||
// The source item (AE's Layer.source): a Comp for a precomp layer, a Footage for footage, solid
|
||||
// and model layers, null for text, shape, null, camera and light layers (#809).
|
||||
get source() {
|
||||
var s = __h(__SOURCE, this.__c, this.__l);
|
||||
if (s === null) return null;
|
||||
if (s[0] === 'comp') return new Comp(s[1]);
|
||||
return new Footage([s[2], s[3], s[4], s[5], s[6], s[7], s[0], null, null]);
|
||||
},
|
||||
__group: function (k) {
|
||||
var g = __child(this.__c, this.__l, '', k);
|
||||
if (g === undefined) __err('Layer "' + this.name + '" has no property "' + k + '"');
|
||||
@@ -810,10 +818,18 @@ Prop.prototype = {
|
||||
|
||||
var __propHandler = {
|
||||
get: function (t, k) {
|
||||
if (typeof k !== 'string' || k in t) return t[k];
|
||||
if (typeof k !== 'string') return t[k];
|
||||
// A layer-valued property (a Layer Control's Layer) is named as its layer (#809).
|
||||
if (k === 'name' && t.__info[2] === 'layer') {
|
||||
var lv = t.value;
|
||||
if (lv !== null && lv !== undefined && lv.__isLayer) return lv.name;
|
||||
}
|
||||
if (k in t) return t[k];
|
||||
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 !== null && x !== undefined && x.__isGroup) return x;
|
||||
return typeof x === 'function' ? x.bind(v) : x;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -185,6 +185,7 @@ fn native_host(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsV
|
||||
t: secs(num(args, 8, ctx)?),
|
||||
},
|
||||
14 => Req::Project,
|
||||
15 => Req::SourceInfo { comp: id(args, 1, ctx)?, layer: id(args, 2, ctx)? },
|
||||
_ => return Err(JsNativeError::typ().with_message("unknown host request").into()),
|
||||
};
|
||||
let found = FRAMES.with(|f| {
|
||||
|
||||
@@ -1251,3 +1251,37 @@ fn pending_text_style_baseline_setters_are_visible_to_expression_getters() {
|
||||
assert_eq!(x.num("thisComp.layer('Title').text.sourceText.style.setBaselineOption('superscript').isSuperscript ? 1 : 0", 0.0), 1.0);
|
||||
assert_eq!(x.text("var s = text.sourceText.style.setSuperscript(true); s.setText(s.isSuperscript ? 'super' : 'normal')", 0.0), "super");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_source_is_the_layers_source_item() {
|
||||
let mut f = fx();
|
||||
// A solid's source is an item of the solid's size and name (#809).
|
||||
assert_close!(f.num("thisComp.layer('A').source.width", 0.0), 20.0);
|
||||
assert_close!(f.num("thisComp.layer('A').source.height", 0.0), 20.0);
|
||||
assert_close!(f.num("thisComp.layer('A').source.name == 'A' ? 1 : 0", 0.0), 1.0);
|
||||
// Text and shape layers have no source item.
|
||||
assert_close!(f.num("thisComp.layer('Title').source == null ? 1 : 0", 0.0), 1.0);
|
||||
// A precomp layer's source is its composition.
|
||||
let main = f.p.comp(f.cid).unwrap().clone();
|
||||
let other = f.p.items.values().find(|i| i.name == "Other").unwrap().id;
|
||||
let pre = build::layer(&mut f.p, &main, "Pre", LayerSource::Comp { item: other }, (64, 32), None);
|
||||
f.p.comp_mut(f.cid).unwrap().layers.push(pre);
|
||||
assert_close!(f.num("thisComp.layer('Pre').source.width", 0.0), 64.0);
|
||||
assert_close!(f.num("thisComp.layer('Pre').source.numLayers", 0.0), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_control_property_stands_for_its_layer() {
|
||||
let mut f = fx();
|
||||
// Layer A gets a Layer Control ("Map") that points at layer B (#809).
|
||||
let spec = effectcraft_effects::find("ec.control.layer").unwrap();
|
||||
let mut map = group_push(&mut f.p, |ids| effectcraft_effects::instantiate(spec, ids, "Map", [20.0, 20.0]));
|
||||
map.prop_mut("layer").unwrap().value = Value::Layer(Some(f.b.0));
|
||||
f.p.comp_mut(f.cid).unwrap().layers[0].props.sub_mut("effects").unwrap().children.push(map.into());
|
||||
// effect("Map")(1) is the layer itself: its name, index, transform and value.
|
||||
assert_close!(f.num("effect('Map')(1).name == 'B' ? 1 : 0", 0.0), 1.0);
|
||||
assert_close!(f.num("effect('Map')(1).index", 0.0), 2.0);
|
||||
assert_close!(f.num("effect('Map')(1).transform.position[0]", 0.0), 100.0);
|
||||
assert_close!(f.num("effect('Map')(1).transform.scale[0]", 0.0), 100.0);
|
||||
assert_close!(f.num("effect('Map')(1).value.name == 'B' ? 1 : 0", 0.0), 1.0);
|
||||
}
|
||||
Reference in new issue
Block a user