375 lines
16 KiB
Rust
375 lines
16 KiB
Rust
//! Expression tools: the error list behind the expression error bar, and the Expression
|
|
//! Language menu (the ⓕ button next to an expression), written from the public Expression
|
|
//! Language Reference.
|
|
|
|
use effectcraft_project::{Expression, ItemId, Layer, Property};
|
|
use effectcraft_render::EvalCtx;
|
|
use effectcraft_time::Tick;
|
|
use serde::Serialize;
|
|
use serde_json::{Value, json};
|
|
|
|
use super::{CommandSpec, comp_id, f_p};
|
|
use crate::{EngineError, Result, Session, query};
|
|
|
|
/// One failing expression.
|
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
|
pub struct ExprError {
|
|
pub comp: u64,
|
|
pub layer: u64,
|
|
#[serde(rename = "layerIndex")]
|
|
pub layer_index: usize,
|
|
#[serde(rename = "layerName")]
|
|
pub layer_name: String,
|
|
pub prop: u64,
|
|
/// Match path (`transform/opacity`).
|
|
pub path: String,
|
|
/// Property name path as shown in the timeline (`Transform/Opacity`).
|
|
pub name: String,
|
|
pub message: String,
|
|
/// The expression is disabled (it failed to compile).
|
|
pub disabled: bool,
|
|
}
|
|
|
|
impl ExprError {
|
|
/// The error bar's text: `Error at line 1 in property 'Opacity' of layer 1 ('Solid') in comp 'Main'. …`
|
|
pub fn describe(&self, comp_name: &str) -> String {
|
|
let prop = self.name.rsplit('/').next().unwrap_or(&self.name);
|
|
format!("{} — property '{}' of layer {} ('{}') in comp '{}'", self.message, prop, self.layer_index, self.layer_name, comp_name)
|
|
}
|
|
}
|
|
|
|
/// Every failing expression of comp `cid` at comp time `t`: evaluation errors of enabled
|
|
/// expressions and the syntax errors that disabled others.
|
|
pub fn errors(s: &Session, cid: ItemId, t: Tick) -> Vec<ExprError> {
|
|
let Some(comp) = s.project.comp(cid) else { return vec![] };
|
|
let ctx = EvalCtx { footage: Some(s.footage.as_ref()), expr: s.expr.as_deref(), ..EvalCtx::new(&s.project, cid, comp, t) };
|
|
let mut out = vec![];
|
|
for (i, l) in comp.layers.iter().enumerate() {
|
|
let mut props: Vec<(&Property, &Expression)> = vec![];
|
|
l.props.walk("", &mut |_, p| {
|
|
if let Some(e) = p.expr.as_ref().filter(|e| !e.text.trim().is_empty()) {
|
|
props.push((p, e));
|
|
}
|
|
});
|
|
for (p, e) in props {
|
|
let msg = if e.enabled {
|
|
match &ctx.expr {
|
|
Some(h) => h.eval(&ctx, l, p, &p.value_at(l.layer_time(t))).err(),
|
|
None => None,
|
|
}
|
|
} else {
|
|
s.expr_check.and_then(|check| check(&e.text).err())
|
|
};
|
|
if let Some(message) = msg {
|
|
out.push(error_of(cid, i, l, p, message, !e.enabled));
|
|
}
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
fn error_of(cid: ItemId, i: usize, l: &Layer, p: &Property, message: String, disabled: bool) -> ExprError {
|
|
ExprError {
|
|
comp: cid.0,
|
|
layer: l.id.0,
|
|
layer_index: i + 1,
|
|
layer_name: l.name.clone(),
|
|
prop: p.uid,
|
|
path: super::match_path_of(&l.props, p.uid).unwrap_or_default(),
|
|
name: l.props.name_path_of(p.uid).unwrap_or_else(|| p.name.clone()),
|
|
message,
|
|
disabled,
|
|
}
|
|
}
|
|
|
|
fn errors_cmd(s: &mut Session, p: &Value) -> Result<Value> {
|
|
let cid = comp_id(s, p)?;
|
|
let t = f_p(p, "time").map(Tick::from_seconds_f64).unwrap_or_else(|| s.time_of(cid));
|
|
let name = s.project.item(cid).map(|i| i.name.clone()).ok_or(EngineError::NoComp)?;
|
|
let list = errors(s, cid, t);
|
|
let text: Vec<String> = list.iter().map(|e| e.describe(&name)).collect();
|
|
Ok(json!({"comp": cid.0, "time": t.seconds(), "count": list.len(), "errors": list, "text": text}))
|
|
}
|
|
|
|
/// The Expression Language menu: (category, [(label, inserted text)]). Submenus are nested
|
|
/// with `▸` in the category name.
|
|
pub fn language_menu() -> Vec<(&'static str, Vec<(&'static str, &'static str)>)> {
|
|
vec![
|
|
(
|
|
"Global",
|
|
vec![
|
|
("comp(name)", "comp(\"name\")"),
|
|
("footage(name)", "footage(\"name\")"),
|
|
("thisComp", "thisComp"),
|
|
("thisLayer", "thisLayer"),
|
|
("thisProperty", "thisProperty"),
|
|
("thisProject", "thisProject"),
|
|
("time", "time"),
|
|
("colorDepth", "colorDepth"),
|
|
("posterizeTime(framesPerSecond)", "posterizeTime(framesPerSecond)"),
|
|
("value", "value"),
|
|
],
|
|
),
|
|
(
|
|
"Vector Math",
|
|
vec![
|
|
("add(vec1, vec2)", "add(vec1, vec2)"),
|
|
("sub(vec1, vec2)", "sub(vec1, vec2)"),
|
|
("mul(vec, amount)", "mul(vec, amount)"),
|
|
("div(vec, amount)", "div(vec, amount)"),
|
|
("clamp(value, limit1, limit2)", "clamp(value, limit1, limit2)"),
|
|
("dot(vec1, vec2)", "dot(vec1, vec2)"),
|
|
("cross(vec1, vec2)", "cross(vec1, vec2)"),
|
|
("normalize(vec)", "normalize(vec)"),
|
|
("length(vec)", "length(vec)"),
|
|
("length(point1, point2)", "length(point1, point2)"),
|
|
("lookAt(fromPoint, atPoint)", "lookAt(fromPoint, atPoint)"),
|
|
],
|
|
),
|
|
(
|
|
"Random Numbers",
|
|
vec![
|
|
("seedRandom(offset, timeless=false)", "seedRandom(offset, timeless=false)"),
|
|
("random()", "random()"),
|
|
("random(maxValOrArray)", "random(maxValOrArray)"),
|
|
("random(minValOrArray, maxValOrArray)", "random(minValOrArray, maxValOrArray)"),
|
|
("gaussRandom()", "gaussRandom()"),
|
|
("gaussRandom(maxValOrArray)", "gaussRandom(maxValOrArray)"),
|
|
("gaussRandom(minValOrArray, maxValOrArray)", "gaussRandom(minValOrArray, maxValOrArray)"),
|
|
("noise(valOrArray)", "noise(valOrArray)"),
|
|
],
|
|
),
|
|
(
|
|
"Interpolation",
|
|
vec![
|
|
("linear(t, value1, value2)", "linear(t, value1, value2)"),
|
|
("linear(t, tMin, tMax, value1, value2)", "linear(t, tMin, tMax, value1, value2)"),
|
|
("ease(t, value1, value2)", "ease(t, value1, value2)"),
|
|
("ease(t, tMin, tMax, value1, value2)", "ease(t, tMin, tMax, value1, value2)"),
|
|
("easeIn(t, value1, value2)", "easeIn(t, value1, value2)"),
|
|
("easeIn(t, tMin, tMax, value1, value2)", "easeIn(t, tMin, tMax, value1, value2)"),
|
|
("easeOut(t, value1, value2)", "easeOut(t, value1, value2)"),
|
|
("easeOut(t, tMin, tMax, value1, value2)", "easeOut(t, tMin, tMax, value1, value2)"),
|
|
],
|
|
),
|
|
(
|
|
"Color Conversion",
|
|
vec![
|
|
("rgbToHsl(rgbaArray)", "rgbToHsl(rgbaArray)"),
|
|
("hslToRgb(hslaArray)", "hslToRgb(hslaArray)"),
|
|
("hexToRgb(hexString)", "hexToRgb(hexString)"),
|
|
],
|
|
),
|
|
("Other Math", vec![("degreesToRadians(degrees)", "degreesToRadians(degrees)"), ("radiansToDegrees(radians)", "radiansToDegrees(radians)")]),
|
|
(
|
|
"JavaScript Math",
|
|
vec![
|
|
("Math.cos(value)", "Math.cos(value)"),
|
|
("Math.sin(value)", "Math.sin(value)"),
|
|
("Math.atan2(y, x)", "Math.atan2(y, x)"),
|
|
("Math.sqrt(value)", "Math.sqrt(value)"),
|
|
("Math.abs(value)", "Math.abs(value)"),
|
|
("Math.round(value)", "Math.round(value)"),
|
|
("Math.floor(value)", "Math.floor(value)"),
|
|
("Math.min(value1, value2)", "Math.min(value1, value2)"),
|
|
("Math.max(value1, value2)", "Math.max(value1, value2)"),
|
|
("Math.PI", "Math.PI"),
|
|
],
|
|
),
|
|
(
|
|
"Comp",
|
|
vec![
|
|
("layer(index)", "layer(index)"),
|
|
("layer(name)", "layer(\"name\")"),
|
|
("layer(otherLayer, relIndex)", "layer(otherLayer, relIndex)"),
|
|
("marker", "marker"),
|
|
("numLayers", "numLayers"),
|
|
("activeCamera", "activeCamera"),
|
|
("width", "width"),
|
|
("height", "height"),
|
|
("duration", "duration"),
|
|
("frameDuration", "frameDuration"),
|
|
("pixelAspect", "pixelAspect"),
|
|
("name", "name"),
|
|
("bgColor", "bgColor"),
|
|
("displayStartTime", "displayStartTime"),
|
|
],
|
|
),
|
|
(
|
|
"Footage",
|
|
vec![
|
|
("width", "width"),
|
|
("height", "height"),
|
|
("duration", "duration"),
|
|
("frameDuration", "frameDuration"),
|
|
("pixelAspect", "pixelAspect"),
|
|
("name", "name"),
|
|
("sourceText", "sourceText"),
|
|
("sourceData", "sourceData"),
|
|
("dataValue(dataPath)", "dataValue(dataPath)"),
|
|
("dataKeyCount", "dataKeyCount"),
|
|
],
|
|
),
|
|
(
|
|
"Layer > Sub-objects",
|
|
vec![
|
|
("source", "source"),
|
|
("effect(name)", "effect(\"name\")"),
|
|
("effect(index)", "effect(index)"),
|
|
("mask(name)", "mask(\"name\")"),
|
|
("mask(index)", "mask(index)"),
|
|
],
|
|
),
|
|
(
|
|
"Layer > General",
|
|
vec![
|
|
("width", "width"),
|
|
("height", "height"),
|
|
("index", "index"),
|
|
("parent", "parent"),
|
|
("hasParent", "hasParent"),
|
|
("inPoint", "inPoint"),
|
|
("outPoint", "outPoint"),
|
|
("startTime", "startTime"),
|
|
("hasVideo", "hasVideo"),
|
|
("enabled", "enabled"),
|
|
("active", "active"),
|
|
],
|
|
),
|
|
(
|
|
"Layer > Properties",
|
|
vec![
|
|
("anchorPoint", "anchorPoint"),
|
|
("position", "position"),
|
|
("scale", "scale"),
|
|
("rotation", "rotation"),
|
|
("opacity", "opacity"),
|
|
("marker", "marker"),
|
|
("name", "name"),
|
|
],
|
|
),
|
|
(
|
|
"Layer > Space Transforms",
|
|
vec![
|
|
("toComp(point, t = time)", "toComp(point, t = time)"),
|
|
("fromComp(point, t = time)", "fromComp(point, t = time)"),
|
|
("toWorld(point, t = time)", "toWorld(point, t = time)"),
|
|
("fromWorld(point, t = time)", "fromWorld(point, t = time)"),
|
|
("toCompVec(vec, t = time)", "toCompVec(vec, t = time)"),
|
|
("fromCompVec(vec, t = time)", "fromCompVec(vec, t = time)"),
|
|
("toWorldVec(vec, t = time)", "toWorldVec(vec, t = time)"),
|
|
("fromWorldVec(vec, t = time)", "fromWorldVec(vec, t = time)"),
|
|
],
|
|
),
|
|
(
|
|
"Layer > Other",
|
|
vec![
|
|
("sourceRectAtTime(t = time, includeExtents = false)", "sourceRectAtTime(t = time, includeExtents = false)"),
|
|
("sampleImage(point, radius = [.5, .5], postEffect = true, t = time)", "sampleImage(point, radius = [.5, .5], postEffect = true, t = time)"),
|
|
],
|
|
),
|
|
(
|
|
"Property",
|
|
vec![
|
|
("value", "value"),
|
|
("valueAtTime(t)", "valueAtTime(t)"),
|
|
("velocity", "velocity"),
|
|
("velocityAtTime(t)", "velocityAtTime(t)"),
|
|
("speed", "speed"),
|
|
("speedAtTime(t)", "speedAtTime(t)"),
|
|
("wiggle(freq, amp, octaves = 1, amp_mult = .5, t = time)", "wiggle(freq, amp, octaves = 1, amp_mult = .5, t = time)"),
|
|
("temporalWiggle(freq, amp, octaves = 1, amp_mult = .5, t = time)", "temporalWiggle(freq, amp, octaves = 1, amp_mult = .5, t = time)"),
|
|
("smooth(width = .2, samples = 5, t = time)", "smooth(width = .2, samples = 5, t = time)"),
|
|
("loopIn(type = \"cycle\", numKeyframes = 0)", "loopIn(type = \"cycle\", numKeyframes = 0)"),
|
|
("loopOut(type = \"cycle\", numKeyframes = 0)", "loopOut(type = \"cycle\", numKeyframes = 0)"),
|
|
("loopInDuration(type = \"cycle\", duration = 0)", "loopInDuration(type = \"cycle\", duration = 0)"),
|
|
("loopOutDuration(type = \"cycle\", duration = 0)", "loopOutDuration(type = \"cycle\", duration = 0)"),
|
|
("key(index)", "key(index)"),
|
|
("key(markerName)", "key(markerName)"),
|
|
("nearestKey(t)", "nearestKey(t)"),
|
|
("numKeys", "numKeys"),
|
|
("name", "name"),
|
|
("propertyIndex", "propertyIndex"),
|
|
],
|
|
),
|
|
(
|
|
"Path Property",
|
|
vec![
|
|
("points(t = time)", "points(t = time)"),
|
|
("inTangents(t = time)", "inTangents(t = time)"),
|
|
("outTangents(t = time)", "outTangents(t = time)"),
|
|
("isClosed()", "isClosed()"),
|
|
(
|
|
"createPath(points, inTangents = [], outTangents = [], is_closed = true)",
|
|
"createPath(points, inTangents = [], outTangents = [], is_closed = true)",
|
|
),
|
|
],
|
|
),
|
|
(
|
|
"Text > Style",
|
|
vec![
|
|
("text.sourceText.style", "text.sourceText.style"),
|
|
("getStyleAt(charIndex, t = time)", "getStyleAt(charIndex, t = time)"),
|
|
("createStyle()", "createStyle()"),
|
|
("setFontSize(value)", "setFontSize(value)"),
|
|
("setFillColor(value)", "setFillColor(value)"),
|
|
("setText(value)", "setText(value)"),
|
|
],
|
|
),
|
|
("Text > Expression Selector", vec![("textIndex", "textIndex"), ("textTotal", "textTotal"), ("selectorValue", "selectorValue")]),
|
|
("Key", vec![("value", "value"), ("time", "time"), ("index", "index")]),
|
|
(
|
|
"Marker Key",
|
|
vec![
|
|
("duration", "duration"),
|
|
("comment", "comment"),
|
|
("chapter", "chapter"),
|
|
("url", "url"),
|
|
("frameTarget", "frameTarget"),
|
|
("eventCuePoint", "eventCuePoint"),
|
|
("cuePointName", "cuePointName"),
|
|
("parameters", "parameters"),
|
|
("protectedRegion", "protectedRegion"),
|
|
],
|
|
),
|
|
(
|
|
"Time Conversion",
|
|
vec![
|
|
(
|
|
"timeToFrames(t = time + thisComp.displayStartTime, fps = 1.0 / thisComp.frameDuration, isDuration = false)",
|
|
"timeToFrames(t = time + thisComp.displayStartTime, fps = 1.0 / thisComp.frameDuration, isDuration = false)",
|
|
),
|
|
("framesToTime(frames, fps = 1.0 / thisComp.frameDuration)", "framesToTime(frames, fps = 1.0 / thisComp.frameDuration)"),
|
|
(
|
|
"timeToTimecode(t = time + thisComp.displayStartTime, timecodeBase = 30, isDuration = false)",
|
|
"timeToTimecode(t = time + thisComp.displayStartTime, timecodeBase = 30, isDuration = false)",
|
|
),
|
|
(
|
|
"timeToFeetAndFrames(t = time + thisComp.displayStartTime, fps = 1.0 / thisComp.frameDuration, framesPerFoot = 16, isDuration = false)",
|
|
"timeToFeetAndFrames(t = time + thisComp.displayStartTime, fps = 1.0 / thisComp.frameDuration, framesPerFoot = 16, isDuration = false)",
|
|
),
|
|
(
|
|
"timeToCurrentFormat(t = time + thisComp.displayStartTime, fps = 1.0 / thisComp.frameDuration, isDuration = false)",
|
|
"timeToCurrentFormat(t = time + thisComp.displayStartTime, fps = 1.0 / thisComp.frameDuration, isDuration = false)",
|
|
),
|
|
],
|
|
),
|
|
]
|
|
}
|
|
|
|
fn menu_cmd(_s: &mut Session, _p: &Value) -> Result<Value> {
|
|
Ok(json!(
|
|
language_menu()
|
|
.into_iter()
|
|
.map(|(c, items)| json!({"category": c, "items": items.into_iter().map(|(l, t)| json!({"label": l, "text": t})).collect::<Vec<_>>()}))
|
|
.collect::<Vec<_>>()
|
|
))
|
|
}
|
|
|
|
pub fn specs() -> Vec<CommandSpec> {
|
|
vec![
|
|
query!("expr.errors", "Expression errors (the error bar)", "{comp?, time? (s)}", errors_cmd),
|
|
query!("expr.languageMenu", "Expression Language menu", "{}", menu_cmd),
|
|
]
|
|
}
|