mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
Automation: a layer or comp id sent as a string resolves as an id (#749)
This commit is contained in:
1 parent
9a7d3e7699
commit
bee798c190
3 files changed
+39
-5
No files matched your search
@@ -442,17 +442,34 @@ pub(crate) fn merge_p(p: &Value) -> Option<&str> {
|
||||
str_p(p, "merge")
|
||||
}
|
||||
|
||||
/// `comp`: item id or name; default the active comp.
|
||||
/// `comp`: item id or name (an all-digit string that names no comp is taken as an id, as MCP
|
||||
/// clients often send numbers as strings); default the active comp.
|
||||
pub(crate) fn comp_id(s: &Session, p: &Value) -> Result<ItemId> {
|
||||
let missing = |v: &Value| EngineError::NoSuchComp(v.to_string());
|
||||
match p.get("comp") {
|
||||
Some(v @ Value::Number(n)) => n.as_u64().map(ItemId).filter(|id| s.project.comp(*id).is_some()).ok_or_else(|| missing(v)),
|
||||
Some(v @ Value::String(name)) => s.project.items.values().find(|i| &i.name == name && i.as_comp().is_some()).map(|i| i.id).ok_or_else(|| missing(v)),
|
||||
Some(v @ Value::String(name)) => s
|
||||
.project
|
||||
.items
|
||||
.values()
|
||||
.find(|i| &i.name == name && i.as_comp().is_some())
|
||||
.map(|i| i.id)
|
||||
.or_else(|| digits_id(name).map(ItemId).filter(|id| s.project.comp(*id).is_some()))
|
||||
.ok_or_else(|| missing(v)),
|
||||
_ => s.active_comp_id().ok_or(EngineError::NoComp),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a layer reference: id (number), `#n` / index (1-based), or name.
|
||||
/// An all-ASCII-digit string as a number (`"98"` -> 98); anything else (names, `#n`, signs) is `None`.
|
||||
fn digits_id(s: &str) -> Option<u64> {
|
||||
if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
|
||||
return None;
|
||||
}
|
||||
s.parse().ok()
|
||||
}
|
||||
|
||||
/// Resolve a layer reference: id (number), `#n` / index (1-based), or name. An all-digit string
|
||||
/// that names no layer is resolved as the number it spells.
|
||||
pub(crate) fn resolve_layer(comp: &Comp, v: &Value) -> Option<LayerId> {
|
||||
match v {
|
||||
Value::Number(n) => {
|
||||
@@ -463,7 +480,7 @@ pub(crate) fn resolve_layer(comp: &Comp, v: &Value) -> Option<LayerId> {
|
||||
if let Some(i) = s.strip_prefix('#').and_then(|i| i.parse::<usize>().ok()) {
|
||||
return comp.layers.get(i.checked_sub(1)?).map(|l| l.id);
|
||||
}
|
||||
comp.layers.iter().find(|l| &l.name == s).map(|l| l.id)
|
||||
comp.layers.iter().find(|l| &l.name == s).map(|l| l.id).or_else(|| digits_id(s).and_then(|id| resolve_layer(comp, &Value::from(id))))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -420,3 +420,19 @@ fn camera_focus_commands_undo_and_serde() {
|
||||
assert!(s.execute("camera.linkFocusToLayer", json!({})).is_err());
|
||||
assert!(s.execute("camera.linkFocusToPoi", json!({})).is_ok());
|
||||
}
|
||||
|
||||
/// An id sent as a string (`"98"`, as MCP clients often do) resolves as an id when no name matches.
|
||||
#[test]
|
||||
fn numeric_string_resolves_as_id() {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "T", "width": 320, "height": 180, "duration": 2.0, "frameRate": 30})).unwrap();
|
||||
let l = s.execute("layer.newSolid", json!({"color": "#ff0000"})).unwrap()["layer"].as_u64().unwrap();
|
||||
let cid = s.active_comp_id().unwrap();
|
||||
assert_eq!(s.resolve_comp(Some(&json!(cid.0.to_string()))).unwrap(), cid);
|
||||
assert!(s.resolve_comp(Some(&json!("999999"))).is_err());
|
||||
let comp = s.project.comp(cid).unwrap();
|
||||
assert_eq!(crate::commands::resolve_layer(comp, &json!(l.to_string())), Some(LayerId(l)));
|
||||
assert_eq!(crate::commands::resolve_layer(comp, &json!("999999")), None);
|
||||
let r = s.execute("layer.tree", json!({"layer": l.to_string(), "comp": cid.0.to_string()})).unwrap();
|
||||
assert_eq!(r["id"].as_u64(), Some(l), "{r}");
|
||||
}
|
||||
+2
-1
@@ -123,7 +123,8 @@ See [MCP conventions](mcp.md) for core tools, resources, strict arguments, and r
|
||||
| `render_preview` | Render a PNG without writing a file (`comp`, `time`, `max_side`, `transparent`). |
|
||||
|
||||
Layers are referenced by id (from `get_comp`), `"#n"` (1-based index from the top) or name. Comps are
|
||||
referenced by id or name, and default to the active comp. Property paths come from `get_layer`, for
|
||||
referenced by id or name, and default to the active comp. An id may also be sent as a string (`"98"`):
|
||||
an all-digit string that matches no name is taken as the id. Property paths come from `get_layer`, for
|
||||
example `transform/position`, `transform/opacity`, `effects/#1/blurriness` or `@57` (by uid). Times
|
||||
are in seconds. Keyframe times are layer time, which equals comp time unless the layer is offset or
|
||||
stretched.
|
||||
|
||||
Reference in new issue
Block a user