From f816cd5e389d2b4e86967f5189f00f3098487d07 Mon Sep 17 00:00:00 2001 From: redonion23 <199118131+redonion23@users.noreply.github.com> Date: Tue, 13 Oct 2026 03:04:12 +0200 Subject: [PATCH] Scripting: a stroke's Dashes use After Effects' match names (#815) Dashes reported its children as `dash`, `gap`, `offset`, so `property("ADBE Vector Stroke Dash 1")` was null and addProperty threw. They now report ADBE Vector Stroke Dash 1-3 / Gap 1-3 / Offset. addProperty with one of those names returns the existing property (EffectCraft's Dashes always holds Dash 1, Gap 1 and Offset) or adds the Dash/Gap 2 or 3 pair through shape.dashes.add. Lookups by the old ids still work. Co-Authored-By: Claude Opus 5.5 --- crates/script/src/matchnames.rs | 19 +++++++++++++++++++ crates/script/src/model.rs | 9 +++++++++ crates/script/src/prelude.js | 9 +++++++++ crates/script/src/tests.rs | 31 +++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/crates/script/src/matchnames.rs b/crates/script/src/matchnames.rs index d8a8f3aa..60a40e69 100644 --- a/crates/script/src/matchnames.rs +++ b/crates/script/src/matchnames.rs @@ -72,6 +72,22 @@ const TEXT_PATH_OPTIONS: &[(&str, &str)] = &[ ("lastMargin", "ADBE Text Last Margin"), ]; +// A stroke's Dashes, as After Effects' scripting API names them (#815). +const STROKE_DASHES: &[(&str, &str)] = &[ + ("dash", "ADBE Vector Stroke Dash 1"), + ("gap", "ADBE Vector Stroke Gap 1"), + ("dash2", "ADBE Vector Stroke Dash 2"), + ("gap2", "ADBE Vector Stroke Gap 2"), + ("dash3", "ADBE Vector Stroke Dash 3"), + ("gap3", "ADBE Vector Stroke Gap 3"), + ("offset", "ADBE Vector Stroke Offset"), +]; + +/// The Dashes child id (`dash`, `gap2`, `offset`…) for an After Effects match name. +pub fn stroke_dash(name: &str) -> Option<&'static str> { + STROKE_DASHES.iter().find(|(_, m)| *m == name).map(|(id, _)| *id) +} + const TEXT_ANIMATOR: &[(&str, &str)] = &[("selectors", "ADBE Text Selectors"), ("properties", "ADBE Text Animator Properties")]; const TEXT_SELECTORS: &[(&str, &str)] = @@ -447,6 +463,9 @@ pub fn node(chain: &[&Node]) -> String { if let Some((_, _, m)) = SHAPE_PROPS.iter().find(|(item, prop, _)| *item == pid && *prop == id) { return m.to_string(); } + if pid == "dashes" { + return find(STROKE_DASHES, id).map(str::to_string).unwrap_or_else(own); + } own() } _ => own(), diff --git a/crates/script/src/model.rs b/crates/script/src/model.rs index 4445fbbf..9aacdbb4 100644 --- a/crates/script/src/model.rs +++ b/crates/script/src/model.rs @@ -626,6 +626,15 @@ fn resolve_add(s: &Session, a: &J) -> R { } } "animators" => json!({"kind": "animator"}), + // A stroke always holds Dash 1, Gap 1 and Offset (Dash 0 draws no dashes); adding one of + // them returns it, Dash/Gap 2 and 3 are added as a pair (`shape.dashes.add`) (#815). + "dashes" if in_group => match matchnames::stroke_dash(name) { + Some(id) => match g.children.iter().find(|n| n.match_id() == id) { + Some(n) => json!({"kind": "existing", "uid": n.uid()}), + None => json!({"kind": "dash", "id": id, "stroke": l.props.parent_of(g.uid).map(|p| p.uid)}), + }, + None => json!({"error": format!("Can not add \"{name}\" to Dashes")}), + }, _ if in_contents && in_group => match matchnames::shape_kind(name) { Some(k) => { json!({"kind": "shape", "shape": k, "group": if l.props.sub("contents").is_some_and(|c| c.uid == g.uid) { J::Null } else { json!(g.uid) }}) diff --git a/crates/script/src/prelude.js b/crates/script/src/prelude.js index 9daac6ab..1f57f9d6 100644 --- a/crates/script/src/prelude.js +++ b/crates/script/src/prelude.js @@ -715,6 +715,15 @@ PropertyGroup.prototype.addProperty = function (name) { var p = { comp: L.__comp, layer: L.__id, kind: r.shape }; if (r.group !== null && r.group !== undefined) p.group = r.group; uid = __call("layer.addShapeItem", p).uid; + } else if (r.kind === "existing") { + uid = r.uid; + } else if (r.kind === "dash") { + // Dash/Gap 2 and 3 come as a pair: add pairs until the one asked for exists. + var dp = { comp: L.__comp, layer: L.__id, prop: r.stroke }; + for (var n = 0; n < 3 && !this.property(name); n++) __call("shape.dashes.add", dp); + var added = this.property(name); + if (!added) throw __err("Can not add \"" + name + "\" to Dashes"); + return added; } else if (r.kind === "animator") { uid = __call("layer.addTextAnimator", { comp: L.__comp, layer: L.__id, properties: [] }).animator; } else if (r.kind === "animatorProperty") { diff --git a/crates/script/src/tests.rs b/crates/script/src/tests.rs index 303bd820..a4930f47 100644 --- a/crates/script/src/tests.rs +++ b/crates/script/src/tests.rs @@ -828,6 +828,37 @@ fn script_remove_keys_batches_a_range_as_one_edit() { assert!(invalid.error.is_some()); } +/// #815: a stroke's Dashes use After Effects' match names, and addProperty with them returns +/// Dash 1 / Gap 1 / Offset or adds the Dash 2 / Gap 2 pair. +#[test] +fn script_stroke_dashes_use_after_effects_match_names() { + let mut s = session(); + let o = ok( + &mut s, + "function names(g) { var a = []; for (var i = 1; i <= g.numProperties; i++) a.push(g.property(i).matchName); return a.join(','); }\ + var comp = app.project.items.addComp('Names', 100, 100, 1, 10, 25);\ + var contents = comp.layers.addShape().property('ADBE Root Vectors Group'); contents.addProperty('ADBE Vector Shape - Rect');\ + var d = contents.addProperty('ADBE Vector Graphic - Stroke').property('ADBE Vector Stroke Dashes');\ + var before = names(d);\ + var dash1 = d.addProperty('ADBE Vector Stroke Dash 1'); dash1.setValue(12);\ + var gap2 = d.addProperty('ADBE Vector Stroke Gap 2');\ + var err = ''; try { d.addProperty('ADBE Vector Stroke Dash 9'); } catch (e) { err = e.message; }\ + [before, dash1.matchName, d.property('ADBE Vector Stroke Dash 1').value, d.property('ADBE Vector Stroke Offset').matchName, gap2.matchName, names(d), err]", + ); + assert_eq!( + o.result, + json!([ + "ADBE Vector Stroke Dash 1,ADBE Vector Stroke Gap 1,ADBE Vector Stroke Offset", + "ADBE Vector Stroke Dash 1", + 12, + "ADBE Vector Stroke Offset", + "ADBE Vector Stroke Gap 2", + "ADBE Vector Stroke Dash 1,ADBE Vector Stroke Gap 1,ADBE Vector Stroke Dash 2,ADBE Vector Stroke Gap 2,ADBE Vector Stroke Offset", + "Can not add \"ADBE Vector Stroke Dash 9\" to Dashes" + ]) + ); +} + #[test] fn script_layer_comment_and_unnamed_marker_params_round_trip() { let mut s = session();