mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-29 20:47:18 -06:00
Merge pull request #1164 from nanox333/fix/1063-text-animator-stacked
Scripts: text animator properties resolve by their After Effects names (refs #1063)
This commit is contained in:
@@ -142,6 +142,7 @@ fn add_animator_prop(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
let layer = s.project.comp(cid).and_then(|c| c.layer(lid)).cloned().ok_or_else(|| bad(C, "no layer"))?;
|
||||
let auid = animator_uid(s, &layer, p, C)?;
|
||||
let mut index = 0;
|
||||
s.edit("Add Text Animator Property", None, |proj, _| {
|
||||
let mut next = proj.next_id;
|
||||
let mut ids = Ids(&mut next);
|
||||
@@ -151,10 +152,13 @@ fn add_animator_prop(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
for k in &kinds {
|
||||
add_props(&mut ids, anim, k, three);
|
||||
}
|
||||
// The requested property's 1-based position in the Properties group. A companion (such as
|
||||
// Tracking Type) is added before it, so the first new child is not always the one asked for.
|
||||
index = kinds.first().and_then(|k| anim.sub("properties")?.children.iter().position(|n| n.match_id() == k.as_str())).map_or(0, |i| i + 1);
|
||||
proj.next_id = next;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(json!({"animator": auid}))
|
||||
Ok(json!({"animator": auid, "index": index}))
|
||||
}
|
||||
|
||||
pub(crate) fn add_selector(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
@@ -90,6 +90,44 @@ pub fn stroke_dash(name: &str) -> Option<&'static str> {
|
||||
|
||||
const TEXT_ANIMATOR: &[(&str, &str)] = &[("selectors", "ADBE Text Selectors"), ("properties", "ADBE Text Animator Properties")];
|
||||
|
||||
/// Animator property ids and their stable After Effects scripting names. Shared by lookup,
|
||||
/// `.matchName`, and `addProperty` so the same property has one name in every direction.
|
||||
pub const TEXT_ANIMATOR_PROPERTIES: &[(&str, &str)] = &[
|
||||
("anchor", "ADBE Text Anchor Point 3D"),
|
||||
("position", "ADBE Text Position 3D"),
|
||||
("scale", "ADBE Text Scale 3D"),
|
||||
("skew", "ADBE Text Skew"),
|
||||
("rotation", "ADBE Text Rotation"),
|
||||
("opacity", "ADBE Text Opacity"),
|
||||
("fillColor", "ADBE Text Fill Color"),
|
||||
("fillHue", "ADBE Text Fill Hue"),
|
||||
("fillSaturation", "ADBE Text Fill Saturation"),
|
||||
("fillBrightness", "ADBE Text Fill Brightness"),
|
||||
("fillOpacity", "ADBE Text Fill Opacity"),
|
||||
("strokeColor", "ADBE Text Stroke Color"),
|
||||
("strokeHue", "ADBE Text Stroke Hue"),
|
||||
("strokeSaturation", "ADBE Text Stroke Saturation"),
|
||||
("strokeBrightness", "ADBE Text Stroke Brightness"),
|
||||
("strokeOpacity", "ADBE Text Stroke Opacity"),
|
||||
("strokeWidth", "ADBE Text Stroke Width"),
|
||||
("tracking", "ADBE Text Tracking Amount"),
|
||||
("lineAnchor", "ADBE Text Line Anchor"),
|
||||
("lineSpacing", "ADBE Text Line Spacing"),
|
||||
("characterOffset", "ADBE Text Character Offset"),
|
||||
("characterValue", "ADBE Text Character Replace"),
|
||||
("blur", "ADBE Text Blur"),
|
||||
];
|
||||
|
||||
/// Companion properties that exist below an animator but are not kinds `addProperty` accepts:
|
||||
/// they come with Tracking (Track Type), Skew (Skew Axis) and per-character 3D Rotation
|
||||
/// (X and Y Rotation). Lookup and `.matchName` know them; `addProperty` does not.
|
||||
pub const TEXT_ANIMATOR_COMPANIONS: &[(&str, &str)] = &[
|
||||
("trackingType", "ADBE Text Track Type"),
|
||||
("skewAxis", "ADBE Text Skew Axis"),
|
||||
("rotationX", "ADBE Text Rotation X"),
|
||||
("rotationY", "ADBE Text Rotation Y"),
|
||||
];
|
||||
|
||||
const TEXT_SELECTORS: &[(&str, &str)] =
|
||||
&[("rangeSelector", "ADBE Text Selector"), ("wigglySelector", "ADBE Text Wiggly Selector"), ("expressionSelector", "ADBE Text Expressible Selector")];
|
||||
|
||||
@@ -487,6 +525,9 @@ pub fn node(chain: &[&Node]) -> String {
|
||||
"text" if pid == "animators" => "ADBE Text Animator".into(),
|
||||
"text" if pid == "selectors" => find(TEXT_SELECTORS, id).map(str::to_string).unwrap_or_else(own),
|
||||
"text" if pid == "rangeSelector" => find(TEXT_RANGE_SELECTOR, id).map(str::to_string).unwrap_or_else(own),
|
||||
"text" if pid == "properties" && chain.len() == 5 && chain.get(1).is_some_and(|n| n.match_id() == "animators") => {
|
||||
find(TEXT_ANIMATOR_PROPERTIES, id).or_else(|| find(TEXT_ANIMATOR_COMPANIONS, id)).map(str::to_string).unwrap_or_else(own)
|
||||
}
|
||||
"text" if grand.map(|g| g.match_id()) == Some("animators") => find(TEXT_ANIMATOR, id).map(str::to_string).unwrap_or_else(own),
|
||||
"cameraOptions" => find(CAMERA, id).map(str::to_string).unwrap_or_else(own),
|
||||
"lightOptions" => find(LIGHT, id).map(str::to_string).unwrap_or_else(own),
|
||||
@@ -537,6 +578,30 @@ mod tests {
|
||||
assert_eq!(shape_kind("nope"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_animator_property_names_are_canonical_and_scoped() {
|
||||
let n = |m: &str| Node::from(effectcraft_project::PropGroup::new(1, m, m));
|
||||
let (text, animators, animator, properties) = (n("text"), n("animators"), n("animator"), n("properties"));
|
||||
for (id, name) in TEXT_ANIMATOR_PROPERTIES {
|
||||
let prop = n(id);
|
||||
assert_eq!(node(&[&text, &animators, &animator, &properties, &prop]), *name);
|
||||
assert_eq!(node(&[&properties, &prop]), *id, "an unrelated Properties group keeps its own ids");
|
||||
}
|
||||
}
|
||||
|
||||
/// #1063: the companions an animator property brings (Track Type, Skew Axis, X / Y Rotation)
|
||||
/// have their After Effects names too, though `addProperty` does not accept them.
|
||||
#[test]
|
||||
fn text_animator_companions_have_after_effects_names() {
|
||||
let n = |m: &str| Node::from(effectcraft_project::PropGroup::new(1, m, m));
|
||||
let (text, animators, animator, properties) = (n("text"), n("animators"), n("animator"), n("properties"));
|
||||
for (id, name) in TEXT_ANIMATOR_COMPANIONS {
|
||||
let prop = n(id);
|
||||
assert_eq!(node(&[&text, &animators, &animator, &properties, &prop]), *name);
|
||||
assert!(!TEXT_ANIMATOR_PROPERTIES.iter().any(|(k, m)| k == id || m == name), "{id} is not an addProperty kind");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_range_selector_names() {
|
||||
let n = |m: &str| Node::from(effectcraft_project::PropGroup::new(1, m, m));
|
||||
|
||||
@@ -743,36 +743,12 @@ fn resolve_add(s: &Session, a: &J) -> R {
|
||||
/// A text animator property (`layer.addTextAnimatorProperty` key) from an After Effects match
|
||||
/// name or a display name.
|
||||
fn text_animator_property(name: &str) -> Option<&'static str> {
|
||||
const AE: &[(&str, &str)] = &[
|
||||
("ADBE Text Anchor Point 3D", "anchor"),
|
||||
("ADBE Text Position 3D", "position"),
|
||||
("ADBE Text Scale 3D", "scale"),
|
||||
("ADBE Text Skew", "skew"),
|
||||
("ADBE Text Rotation", "rotation"),
|
||||
("ADBE Text Opacity", "opacity"),
|
||||
("ADBE Text Fill Color", "fillColor"),
|
||||
("ADBE Text Fill Hue", "fillHue"),
|
||||
("ADBE Text Fill Saturation", "fillSaturation"),
|
||||
("ADBE Text Fill Brightness", "fillBrightness"),
|
||||
("ADBE Text Fill Opacity", "fillOpacity"),
|
||||
("ADBE Text Stroke Color", "strokeColor"),
|
||||
("ADBE Text Stroke Hue", "strokeHue"),
|
||||
("ADBE Text Stroke Saturation", "strokeSaturation"),
|
||||
("ADBE Text Stroke Brightness", "strokeBrightness"),
|
||||
("ADBE Text Stroke Opacity", "strokeOpacity"),
|
||||
("ADBE Text Stroke Width", "strokeWidth"),
|
||||
("ADBE Text Tracking Amount", "tracking"),
|
||||
("ADBE Text Line Anchor", "lineAnchor"),
|
||||
("ADBE Text Line Spacing", "lineSpacing"),
|
||||
("ADBE Text Character Offset", "characterOffset"),
|
||||
("ADBE Text Character Replace", "characterValue"),
|
||||
("ADBE Text Blur", "blur"),
|
||||
];
|
||||
if let Some((_, k)) = AE.iter().find(|(m, _)| m.eq_ignore_ascii_case(name)) {
|
||||
let table = matchnames::TEXT_ANIMATOR_PROPERTIES;
|
||||
if let Some((k, _)) = table.iter().find(|(_, m)| m.eq_ignore_ascii_case(name)) {
|
||||
return Some(k);
|
||||
}
|
||||
let n = loose(name);
|
||||
AE.iter().map(|(_, k)| *k).find(|k| loose(k) == n || (n == "anchorpoint" && *k == "anchor") || (n == "trackingamount" && *k == "tracking"))
|
||||
table.iter().map(|(k, _)| *k).find(|k| loose(k) == n || (n == "anchorpoint" && *k == "anchor") || (n == "trackingamount" && *k == "tracking"))
|
||||
}
|
||||
|
||||
/// `app.findMenuCommandId(name)`: 1-based index of the command whose label matches (menu items
|
||||
|
||||
@@ -734,9 +734,8 @@ PropertyGroup.prototype.addProperty = function (name) {
|
||||
} else if (r.kind === "animator") {
|
||||
uid = __call("layer.addTextAnimator", { comp: L.__comp, layer: L.__id, properties: [] }).animator;
|
||||
} else if (r.kind === "animatorProperty") {
|
||||
var before = this.numProperties;
|
||||
__call("layer.addTextAnimatorProperty", { comp: L.__comp, layer: L.__id, animator: r.animator, property: r.property });
|
||||
return this.property(before + 1);
|
||||
var added = __call("layer.addTextAnimatorProperty", { comp: L.__comp, layer: L.__id, animator: r.animator, property: r.property });
|
||||
return this.property(added.index);
|
||||
} else if (r.kind === "selector") {
|
||||
var n0 = this.numProperties;
|
||||
__call("layer.addTextSelector", { comp: L.__comp, layer: L.__id, animator: r.animator, kind: r.selector });
|
||||
|
||||
@@ -1719,6 +1719,50 @@ fn layer_property_resolves_transform_and_source_text_shortcuts() {
|
||||
assert_eq!(layer(c, "s").props.prop("transform/opacity").unwrap().value, KV::Scalar(25.0));
|
||||
}
|
||||
|
||||
/// A text animator property added by its After Effects name is the one asked for, not its Tracking
|
||||
/// Type companion, and it can be found again by that name (#1063).
|
||||
#[test]
|
||||
fn scripted_text_animator_property_is_the_one_asked_for() {
|
||||
let mut s = session();
|
||||
let o = ok(
|
||||
&mut s,
|
||||
r#"
|
||||
var c = app.project.items.addComp("Track", 960, 300, 1, 3, 30);
|
||||
var t = c.layers.addText("MOVARA");
|
||||
var a = t.property("ADBE Text Properties").property("ADBE Text Animators").addProperty("ADBE Text Animator");
|
||||
var props = a.property("ADBE Text Animator Properties");
|
||||
var added = props.addProperty("ADBE Text Tracking Amount");
|
||||
added.setValue(20);
|
||||
[added.name, props.property("ADBE Text Tracking Amount").value, props.property("ADBE Text Tracking Amount").name,
|
||||
added.matchName, props.property(added.matchName).propertyIndex]
|
||||
"#,
|
||||
);
|
||||
assert_eq!(o.result, json!(["Tracking Amount", 20, "Tracking Amount", "ADBE Text Tracking Amount", 2]));
|
||||
}
|
||||
|
||||
/// The companion properties added with Tracking and Skew are found by their After Effects names,
|
||||
/// but cannot be added on their own (#1063).
|
||||
#[test]
|
||||
fn text_animator_companion_properties_resolve_by_name() {
|
||||
let mut s = session();
|
||||
let o = ok(
|
||||
&mut s,
|
||||
r#"
|
||||
var c = app.project.items.addComp("Track", 960, 300, 1, 3, 30);
|
||||
var t = c.layers.addText("MOVARA");
|
||||
var props = t.property("ADBE Text Properties").property("ADBE Text Animators").addProperty("ADBE Text Animator").property("ADBE Text Animator Properties");
|
||||
props.addProperty("ADBE Text Tracking Amount");
|
||||
props.addProperty("ADBE Text Skew");
|
||||
var type = props.property("ADBE Text Track Type");
|
||||
var axis = props.property("ADBE Text Skew Axis");
|
||||
var added = "";
|
||||
try { props.addProperty("ADBE Text Track Type"); added = "added"; } catch (e) { added = "refused"; }
|
||||
[type ? type.matchName : null, axis ? axis.matchName : null, added]
|
||||
"#,
|
||||
);
|
||||
assert_eq!(o.result, json!(["ADBE Text Track Type", "ADBE Text Skew Axis", "refused"]));
|
||||
}
|
||||
|
||||
/// #1936: an array whose length doesn't match the property's dimension is an error in After
|
||||
/// Effects, not a value to truncate or broadcast. Two values for a 3-D property stay legal.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user