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 <[email protected]>
This commit is contained in:
redonion23andClaude Opus 5.5 committed 2026-10-13 03:04:12 +02:00
1 parent 470ea7a3af
commit f816cd5e38
4 files changed
+68

No files matched your search

+19
View File
@@ -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(),
+9
View File
@@ -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) }})
+9
View File
@@ -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") {
+31
View File
@@ -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();