mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Part of #785: run and command_batch steps can use earlier results
A run (vectorcraft-cli run, the MCP command_batch tool) had no way to use
an id an earlier step returned. A parameter string that is exactly
$N.path or $last.path now takes that value from step N's result
(0-based), e.g. {"id":"$1.id"}; $$ starts a literal $, and a
reference to nothing fails the step. Objects given to a command are now
used as given: an ids element or id that isn't an object id (or an
unresolved reference) fails instead of the command acting on the
selection. text.setRangeStyle styles the selected type object when no
id is given, as text.setStyle does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c19873e220
commit
b767e850ed
16 files changed
+264
-13
No files matched your search
+1
-1
@@ -98,7 +98,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist
|
||||
- JSON-lines control channel with real egui pointer and keyboard injection.
|
||||
- MCP server with 25 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, any command), attached to the running app or headless. Tool descriptions and replies agree with the engine: format lists come from its format table, `save_file` says it keeps the document's own format, `inspect_document` reports the paint type's characters show, and selection exports report the encoder's warnings.
|
||||
- MCP protocol: five prompts (`prompts/list` / `prompts/get`: poster, icon set, recolour, trace-and-style, export set), `completion/complete` for prompt arguments and template variables read from the live catalogues (formats, effect ids, command ids, trace presets, swatch and object ids), four resource templates (`vectorcraft://object/{id}`, `//command/{id}`, `//effect/{id}`, `//swatch/{name}`) so an agent reads one thing instead of the whole document, and `logging/setLevel` with `notifications/message`. `window.panel` ids are case-insensitive (display labels work too), `vectorcraft://object/{id}` reads through `document.node {summary: true}` (the same reply bytes from one lookup instead of summarizing the whole document: a leaf read on a 2,000-object document drops from ~20 ms to ~0.06 ms), large containers slice with `document.node {depth?, childLimit?}` (truncated levels report `childCount`), `document.inspect` and the `inspect_document` tool slice the same way so a depth-0 read is the layer skeleton (~9.7 KB instead of ~2 MB on a 2,000-object document), `document.find` locates nodes by name/kind/text with ancestor paths, and the CLI sweeps the protocol surface over stdio against the compiled binary. `docs/mcp.md` has a Protocol section covering the capability table and what is deliberately absent (subscriptions, progress, cancellation, sampling, elicitation, Streamable HTTP — all of which need the transport to interleave reads).
|
||||
- Headless CLI (`vectorcraft-cli run`, `convert`, `info`, `bench`, `perf`, `mcp`).
|
||||
- Headless CLI (`vectorcraft-cli run`, `convert`, `info`, `bench`, `perf`, `mcp`). A `run` step, like a `command_batch` step over MCP, can use an earlier step's result (`"$1.id"`, #785).
|
||||
- Actions panel that records and plays back commands.
|
||||
- **UI:** Illustrator 2026 layout restyled to measured values:
|
||||
- Medium Dark theme, categorized and Advanced toolbars (a tool group's flyout opens on a right press, a long press or a press on its corner triangle), 35 pt document tabs, 33 pt panel tabs.
|
||||
|
||||
@@ -269,17 +269,23 @@ fn run(args: &[String]) -> Result<(), String> {
|
||||
} else if !matches!(steps.first(), Some(Step::Cmd(id, _)) if id == "file.new") {
|
||||
h.ensure_document();
|
||||
}
|
||||
// Each step's result, for later steps to refer to (`"$1.id"`, see `vectorcraft_engine::steps`).
|
||||
let mut results = vec![];
|
||||
for step in steps {
|
||||
match step {
|
||||
let r = match step {
|
||||
Step::Cmd(id, params) => {
|
||||
let params = vectorcraft_engine::steps::resolve(¶ms, &results).map_err(|e| format!("{id}: {e}"))?;
|
||||
let r = h.call("engine.execute", json!({"command": id, "params": params})).map_err(|e| format!("{id}: {e}"))?;
|
||||
emit(json!({"step": "cmd", "command": id, "result": r}))?;
|
||||
r
|
||||
}
|
||||
Step::Export(path) => {
|
||||
let r = h.call("app.export", json!({"path": path, "scale": scale})).map_err(|e| format!("export {path}: {e}"))?;
|
||||
emit(json!({"step": "export", "result": r}))?;
|
||||
r
|
||||
}
|
||||
}
|
||||
};
|
||||
results.push(r);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -366,13 +366,23 @@ pub fn color_value(v: &Value) -> Option<Color> {
|
||||
|
||||
/// Objects a command targets: explicit `ids` param or the selection.
|
||||
pub(crate) fn targets(s: &Session, p: &Value) -> Result<Vec<NodeId>> {
|
||||
if let Some(ids) = ids_param(p, "ids") {
|
||||
return Ok(ids);
|
||||
// Objects given are used as given: a value that isn't an object id fails rather than the
|
||||
// command acting on the selection instead (#785).
|
||||
let object_id = |v: &Value| v.as_u64().map(NodeId).ok_or_else(|| EngineError::Other(format!("object ids are non-negative integers, not {v}")));
|
||||
match p.get("ids").filter(|v| !v.is_null()) {
|
||||
Some(Value::Array(a)) => return a.iter().map(object_id).collect(),
|
||||
Some(v) => return Err(EngineError::Other(format!("`ids` must be an array of object ids, not {v}"))),
|
||||
None => {}
|
||||
}
|
||||
if let Some(id) = id_param(p, "id") {
|
||||
return Ok(vec![id]);
|
||||
match p.get("id").filter(|v| !v.is_null()) {
|
||||
Some(v @ Value::Number(_)) => Ok(vec![object_id(v)?]),
|
||||
// A step reference a batch couldn't resolve (other strings are other ids, such as an
|
||||
// effect's).
|
||||
Some(Value::String(r)) if r.starts_with('$') => {
|
||||
Err(EngineError::Other(format!("`id` is the step reference {r:?}, which only `run` and `command_batch` resolve")))
|
||||
}
|
||||
_ => Ok(s.doc()?.selection.objects.clone()),
|
||||
}
|
||||
Ok(s.doc()?.selection.objects.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn ok() -> Result<Value> {
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Character",
|
||||
[],
|
||||
None,
|
||||
"{id, start?: byte, end?: byte (default: all text), font?, style?, size?: pt, leading?: pt|\"auto\", tracking?, kerning?: 1/1000 em|\"auto\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, fill?: colour|\"none\", stroke?: colour|\"none\", strokeWidth?: pt, strokeOptions?: {weight?, cap?, join?, miterLimit?, dash?, dashOffset?, alignDashes?} (as stroke.set: the character stroke), underline?, strikethrough?, allCaps?: bool, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), features?: [\"dlig\", \"-liga\", …], charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\"} style a character range (runs are split at the range ends) → {id, runs}",
|
||||
"{id? (default: the one selected type object), start?: byte, end?: byte (default: all text), font?, style?, size?: pt, leading?: pt|\"auto\", tracking?, kerning?: 1/1000 em|\"auto\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, fill?: colour|\"none\", stroke?: colour|\"none\", strokeWidth?: pt, strokeOptions?: {weight?, cap?, join?, miterLimit?, dash?, dashOffset?, alignDashes?} (as stroke.set: the character stroke), underline?, strikethrough?, allCaps?: bool, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), features?: [\"dlig\", \"-liga\", …], charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\"} style a character range (runs are split at the range ends) → {id, runs}",
|
||||
has_doc,
|
||||
set_range_style
|
||||
),
|
||||
@@ -420,7 +420,14 @@ pub(crate) fn protect_missing_glyphs(before: &[TextRun], runs: &mut Vec<TextRun>
|
||||
|
||||
fn set_range_style(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "text.setRangeStyle";
|
||||
let id = id_param(p, "id").ok_or_else(|| bad(C, "missing `id`"))?;
|
||||
// The type object given, else the one selected (#785).
|
||||
let id = match p.get("id").filter(|v| !v.is_null()) {
|
||||
Some(v) => v.as_u64().map(NodeId).ok_or_else(|| bad(C, format!("`id` must be an object id, not {v}")))?,
|
||||
None => match super::typecmd::text_targets(s, &json!({}), C)?.as_slice() {
|
||||
[one] => *one,
|
||||
_ => return Err(bad(C, "missing `id`: give one, or select one type object")),
|
||||
},
|
||||
};
|
||||
let change = CharChange::parse(p, C, &s.doc()?.doc.setup)?;
|
||||
if change.is_empty() {
|
||||
return Err(bad(C, "nothing to change"));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
pub mod cmd;
|
||||
pub mod guard;
|
||||
pub mod inspect;
|
||||
pub mod steps;
|
||||
mod tooling;
|
||||
pub mod units;
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
//! References between the steps of a batch (`vectorcraft-cli run`, MCP `command_batch`): a
|
||||
//! parameter string that is exactly `$N` or `$N.path` takes that value from the result of step `N`
|
||||
//! (0-based, in the order the steps ran), and `$last…` from the step before. A path goes into
|
||||
//! objects by key and into arrays by index: `$1.id`, `$2.ids.0`, `$last.ids[0]`. `$$` at the start
|
||||
//! of a string stands for one `$` and is never a reference (text such as `$$1.99`). A string that
|
||||
//! looks like a reference but names no value is an error, so a step never runs with a reference
|
||||
//! left in it.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
/// Most nesting [`resolve`] walks into: deeper parameters are left as they are.
|
||||
const MAX_DEPTH: usize = 64;
|
||||
|
||||
/// `params` with every step reference replaced from `results` (the results of the steps run so
|
||||
/// far, in order); an error names a reference that resolves to nothing.
|
||||
pub fn resolve(params: &Value, results: &[Value]) -> Result<Value, String> {
|
||||
resolve_at(params, results, 0)
|
||||
}
|
||||
|
||||
fn resolve_at(v: &Value, results: &[Value], depth: usize) -> Result<Value, String> {
|
||||
if depth > MAX_DEPTH {
|
||||
return Ok(v.clone());
|
||||
}
|
||||
Ok(match v {
|
||||
Value::String(s) => match s.strip_prefix("$$") {
|
||||
Some(rest) => Value::String(format!("${rest}")),
|
||||
None => match parse(s) {
|
||||
Some((step, path)) => lookup(s, step, &path, results)?,
|
||||
None => v.clone(),
|
||||
},
|
||||
},
|
||||
Value::Array(a) => Value::Array(a.iter().map(|x| resolve_at(x, results, depth + 1)).collect::<Result<_, _>>()?),
|
||||
Value::Object(o) => Value::Object(o.iter().map(|(k, x)| Ok((k.clone(), resolve_at(x, results, depth + 1)?))).collect::<Result<_, String>>()?),
|
||||
_ => v.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Which step a reference names.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Step {
|
||||
Index(usize),
|
||||
Last,
|
||||
}
|
||||
|
||||
/// A path segment: an object key or an array index (`.0` is either, as the value decides).
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Seg {
|
||||
Key(String),
|
||||
Index(usize),
|
||||
}
|
||||
|
||||
/// `$N…` or `$last…` → the step and the path after it; `None` for any other string.
|
||||
fn parse(s: &str) -> Option<(Step, Vec<Seg>)> {
|
||||
let rest = s.strip_prefix('$')?;
|
||||
let digits = rest.bytes().take_while(u8::is_ascii_digit).count();
|
||||
let (step, mut rest) = if digits > 0 {
|
||||
let (n, rest) = rest.split_at_checked(digits)?;
|
||||
(Step::Index(n.parse().ok()?), rest)
|
||||
} else {
|
||||
(Step::Last, rest.strip_prefix("last")?)
|
||||
};
|
||||
let mut path = vec![];
|
||||
while !rest.is_empty() {
|
||||
if let Some(r) = rest.strip_prefix('.') {
|
||||
let len = r.bytes().take_while(|b| b.is_ascii_alphanumeric() || *b == b'_').count();
|
||||
let (key, r) = r.split_at_checked(len)?;
|
||||
if key.is_empty() {
|
||||
return None;
|
||||
}
|
||||
path.push(Seg::Key(key.to_string()));
|
||||
rest = r;
|
||||
} else {
|
||||
let (n, r) = rest.strip_prefix('[')?.split_once(']')?;
|
||||
path.push(Seg::Index(n.parse().ok()?));
|
||||
rest = r;
|
||||
}
|
||||
}
|
||||
Some((step, path))
|
||||
}
|
||||
|
||||
fn lookup(text: &str, step: Step, path: &[Seg], results: &[Value]) -> Result<Value, String> {
|
||||
let (i, found) = match step {
|
||||
Step::Index(i) => (i, results.get(i)),
|
||||
Step::Last => (results.len().saturating_sub(1), results.last()),
|
||||
};
|
||||
let mut cur = found.ok_or_else(|| format!("`{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $)"))?;
|
||||
for seg in path {
|
||||
let next = match (seg, cur) {
|
||||
(Seg::Key(k), Value::Object(o)) => o.get(k),
|
||||
(Seg::Key(k), Value::Array(a)) => k.parse::<usize>().ok().and_then(|n| a.get(n)),
|
||||
(Seg::Index(n), Value::Array(a)) => a.get(*n),
|
||||
_ => None,
|
||||
};
|
||||
cur = next.ok_or_else(|| format!("`{text}`: step {i}'s result has no such value (write $$ for a literal $)"))?;
|
||||
}
|
||||
Ok(cur.clone())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn references_take_values_from_earlier_results() {
|
||||
let results = [json!({"index": 0}), json!({"id": 2}), json!({"ids": [5, 6]})];
|
||||
let p = json!({"id": "$1.id", "ids": ["$2.ids.1", "$last.ids[0]"], "nested": {"all": "$2"}, "n": 3, "flag": true});
|
||||
assert_eq!(resolve(&p, &results).unwrap(), json!({"id": 2, "ids": [6, 5], "nested": {"all": {"ids": [5, 6]}}, "n": 3, "flag": true}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_strings_and_escapes_are_left_alone() {
|
||||
let results = [json!({"id": 2})];
|
||||
let p = json!({"text": "costs $1", "price": "$$1.99", "name": "a$1.id", "dollar": "$", "word": "$lastname"});
|
||||
assert_eq!(
|
||||
resolve(&p, &results).unwrap(),
|
||||
json!({"text": "costs $1", "price": "$1.99", "name": "a$1.id", "dollar": "$", "word": "$lastname"})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_reference_to_nothing_is_an_error() {
|
||||
let results = [json!({"id": 2})];
|
||||
for bad in ["$1", "$1.id", "$0.ids", "$0.id.x", "$last.nope", "$7[0]"] {
|
||||
let e = resolve(&json!({"id": bad}), &results).unwrap_err();
|
||||
assert!(e.contains(bad), "{bad}: {e}");
|
||||
}
|
||||
assert!(resolve(&json!({"id": "$last"}), &[]).is_err(), "no step before the first");
|
||||
// Huge indexes and deep nesting don't panic.
|
||||
assert!(resolve(&json!("$99999999999999999999999.id"), &results).is_ok_and(|v| v == json!("$99999999999999999999999.id")));
|
||||
let mut deep = json!("$0.id");
|
||||
for _ in 0..200 {
|
||||
deep = json!([deep]);
|
||||
}
|
||||
assert!(resolve(&deep, &results).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -853,3 +853,30 @@ fn new_layers_and_sublayers_are_numbered_together() {
|
||||
let sub = s.execute("layer.newSublayer", &json!({})).unwrap();
|
||||
assert_eq!(name(&s, &sub), "Layer 4", "the art doesn't count");
|
||||
}
|
||||
|
||||
/// #785: objects given are used as given (a value that isn't an object id fails, never the
|
||||
/// selection instead), and `text.setRangeStyle` works on the selected type object without `id`.
|
||||
#[test]
|
||||
fn given_targets_are_used_as_given_and_range_style_takes_the_selected_type() {
|
||||
let mut s = session();
|
||||
let first = s.execute("text.create", &json!({"x": 10, "y": 60, "text": "first", "size": 30})).unwrap()["id"].clone();
|
||||
s.execute("text.create", &json!({"x": 10, "y": 140, "text": "second", "size": 30})).unwrap();
|
||||
for bad in [
|
||||
json!({"id": "$1.id", "size": 60}),
|
||||
json!({"id": 2.5, "size": 60}),
|
||||
json!({"ids": [first.clone(), "$1.id"], "size": 60}),
|
||||
json!({"ids": 3, "size": 60}),
|
||||
] {
|
||||
assert!(s.execute("text.setStyle", &bad).is_err(), "{bad}");
|
||||
}
|
||||
// Another kind of id (an effect's) is still not an object id.
|
||||
let sel = s.doc().unwrap().selection.objects.clone();
|
||||
assert!(s.execute("effect.apply", &json!({"id": "stylize.dropShadow"})).is_ok());
|
||||
assert_eq!(s.doc().unwrap().selection.objects, sel);
|
||||
// The selected type object (the second) is styled when no id is given.
|
||||
let r = s.execute("text.setRangeStyle", &json!({"start": 0, "end": 3, "style": "Bold"})).unwrap();
|
||||
assert_ne!(r["id"], first);
|
||||
s.execute("select.all", &json!({})).unwrap();
|
||||
assert!(s.execute("text.setRangeStyle", &json!({"start": 0, "end": 3, "size": 40})).is_err(), "two type objects: which one?");
|
||||
assert!(s.execute("text.setRangeStyle", &json!({"id": "two", "size": 40})).is_err());
|
||||
}
|
||||
@@ -129,3 +129,21 @@ fn conventions_resources_and_versioned_results() {
|
||||
rpc(&mut s, "initialize", json!({"protocolVersion":"2025-06-18"}));
|
||||
assert!(rpc(&mut s, "tools/list", json!({}))["result"].get("resultType").is_none());
|
||||
}
|
||||
|
||||
/// #785: a batch step can use an earlier step's result (`"$N.path"`); a reference to nothing
|
||||
/// fails that step.
|
||||
#[test]
|
||||
fn batch_steps_refer_to_earlier_results() {
|
||||
let mut s = Server::new(Box::new(Headless::with_document()));
|
||||
let steps = json!([
|
||||
{"id":"text.create","params":{"x":10,"y":60,"text":"plain and bold words","size":30}},
|
||||
{"id":"shape.rectangle","params":{"x":0,"y":0,"width":20,"height":20}},
|
||||
{"id":"text.setRangeStyle","params":{"id":"$0.id","start":10,"end":14,"style":"Bold"}},
|
||||
{"id":"text.setRangeStyle","params":{"id":"$9.id","start":0,"end":1,"style":"Bold"}}
|
||||
]);
|
||||
let r = call(&mut s, "command_batch", json!({"steps":steps,"stop_on_error":false}));
|
||||
let p = payload(&r);
|
||||
assert_eq!((p["completed"].clone(), p["failed"].clone()), (json!(3), json!(1)), "{p}");
|
||||
assert_eq!(p["results"][2]["result"]["id"], p["results"][0]["result"]["id"], "the text, not the selected rectangle");
|
||||
assert!(p["results"][3]["error"].as_str().unwrap().contains("$9.id"), "{p}");
|
||||
}
|
||||
@@ -96,7 +96,7 @@ pub fn tool_definitions() -> Vec<Value> {
|
||||
tool(
|
||||
"command_batch",
|
||||
"Run several commands",
|
||||
"Run steps in order; each edit is its own undo step. Returns completed, failed and results. Stops on the first error by default.",
|
||||
"Run steps in order; each edit is its own undo step. A param string that is exactly \"$N.path\" (N: 0-based step index) or \"$last.path\" takes that value from an earlier step's result, e.g. {\"id\":\"$1.id\"}; \"$$\" starts a literal \"$\". Returns completed, failed and results. Stops on the first error by default.",
|
||||
obj(
|
||||
json!({"steps": {"type":"array", "items":obj(json!({"id":string("Command id"),"params":{"type":"object"}}), &["id"])}, "stop_on_error":{"type":"boolean", "default":true}}),
|
||||
&["steps"],
|
||||
@@ -899,6 +899,8 @@ fn command_batch(b: &mut dyn Backend, a: &Args) -> Result<ToolResult, String> {
|
||||
Some(v) => v.as_bool().ok_or("`stop_on_error` must be a boolean")?,
|
||||
};
|
||||
let (mut completed, mut failed, mut results) = (0, 0, Vec::new());
|
||||
// Each step's result (null for a failed one), for later steps' `"$N.path"` references.
|
||||
let mut values: Vec<Value> = vec![];
|
||||
for step in steps {
|
||||
let result = vectorcraft_engine::guard::catch_panic(|| {
|
||||
let args = step.as_object().ok_or("each step must be an object")?;
|
||||
@@ -908,7 +910,7 @@ fn command_batch(b: &mut dyn Backend, a: &Args) -> Result<ToolResult, String> {
|
||||
let id = req_str(args, "id")?;
|
||||
let params = match args.get("params") {
|
||||
None | Some(Value::Null) => json!({}),
|
||||
Some(v @ Value::Object(_)) => v.clone(),
|
||||
Some(v @ Value::Object(_)) => vectorcraft_engine::steps::resolve(v, &values)?,
|
||||
Some(_) => return Err("`params` must be an object".into()),
|
||||
};
|
||||
b.call("engine.execute", json!({"command":id,"params":params}))
|
||||
@@ -917,10 +919,12 @@ fn command_batch(b: &mut dyn Backend, a: &Args) -> Result<ToolResult, String> {
|
||||
match result {
|
||||
Ok(v) => {
|
||||
completed += 1;
|
||||
values.push(v.clone());
|
||||
results.push(json!({"ok":true,"result":v}));
|
||||
}
|
||||
Err(e) => {
|
||||
failed += 1;
|
||||
values.push(Value::Null);
|
||||
results.push(json!({"ok":false,"error":e}));
|
||||
if stop {
|
||||
break;
|
||||
|
||||
@@ -3782,3 +3782,8 @@ axis Both Ambos
|
||||
Constrain Restringir
|
||||
Free Transform Transformación libre
|
||||
Perspective Distort Distorsión de perspectiva
|
||||
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` es la referencia a un paso {r}, que solo `run` y `command_batch` resuelven
|
||||
@msg `ids` must be an array of object ids, not {v} `ids` debe ser una lista de identificadores de objeto, no {v}
|
||||
@msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: el resultado del paso {i} no tiene ese valor (escriba $$ para un $ literal)
|
||||
@msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: no hay ningún paso {i} antes de este (los pasos se cuentan desde 0; escriba $$ para un $ literal)
|
||||
@msg object ids are non-negative integers, not {v} los identificadores de objeto son enteros no negativos, no {v}
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3793,3 +3793,8 @@ axis Both Les deux
|
||||
Insert Inline Symbol Insérer un symbole dans le texte
|
||||
ICF Top/Right ICF haut/droite
|
||||
ICF Bottom/Left ICF bas/gauche
|
||||
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` est la référence d’étape {r}, que seuls `run` et `command_batch` résolvent
|
||||
@msg `ids` must be an array of object ids, not {v} `ids` doit être une liste d’identifiants d’objet, pas {v}
|
||||
@msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}` : le résultat de l’étape {i} n’a pas cette valeur (écrivez $$ pour un $ littéral)
|
||||
@msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}` : il n’y a pas d’étape {i} avant celle-ci (les étapes sont comptées à partir de 0 ; écrivez $$ pour un $ littéral)
|
||||
@msg object ids are non-negative integers, not {v} les identifiants d’objet sont des entiers positifs ou nuls, pas {v}
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3782,3 +3782,8 @@ axis Both Entrambi
|
||||
Constrain Vincola
|
||||
Free Transform Trasformazione libera
|
||||
Perspective Distort Distorsione prospettica
|
||||
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` è il riferimento al passaggio {r}, che solo `run` e `command_batch` risolvono
|
||||
@msg `ids` must be an array of object ids, not {v} `ids` deve essere un elenco di ID oggetto, non {v}
|
||||
@msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: il risultato del passaggio {i} non ha questo valore (scrivi $$ per un $ letterale)
|
||||
@msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: non c’è un passaggio {i} prima di questo (i passaggi si contano da 0; scrivi $$ per un $ letterale)
|
||||
@msg object ids are non-negative integers, not {v} gli ID oggetto sono interi non negativi, non {v}
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3774,3 +3774,8 @@ theme Appearance 外観
|
||||
@msg larger than 256 MB 256 MB を超えています
|
||||
@msg the files come to more than 1 GB: add fewer at a time ファイルの合計が 1 GB を超えています: 一度に追加するファイルを減らしてください
|
||||
@msg every numbered name for it is taken in the Fonts folder Fonts フォルダーでは、このファイルの番号付きの名前がすべて使われています
|
||||
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` はステップ参照 {r} です。ステップ参照を解決できるのは `run` と `command_batch` だけです
|
||||
@msg `ids` must be an array of object ids, not {v} `ids` はオブジェクト ID の配列にしてください ({v} は使えません)
|
||||
@msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: ステップ {i} の結果にその値はありません ($ そのものを書くには $$ と入力してください)
|
||||
@msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: この前にステップ {i} はありません (ステップは 0 から数えます。$ そのものを書くには $$ と入力してください)
|
||||
@msg object ids are non-negative integers, not {v} オブジェクト ID は 0 以上の整数です ({v} は使えません)
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3780,3 +3780,8 @@ axis Both Оба
|
||||
Perspective Distort Искажение перспективы
|
||||
Artboard Монтажная область
|
||||
Exit Выйти
|
||||
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` — это ссылка на шаг {r}, которую разрешают только `run` и `command_batch`
|
||||
@msg `ids` must be an array of object ids, not {v} `ids` должен быть массивом идентификаторов объектов, а не {v}
|
||||
@msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: в результате шага {i} нет такого значения (чтобы написать сам $, введите $$)
|
||||
@msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: перед этим шагом нет шага {i} (шаги считаются с 0; чтобы написать сам $, введите $$)
|
||||
@msg object ids are non-negative integers, not {v} идентификаторы объектов — неотрицательные целые числа, а не {v}
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3765,3 +3765,8 @@ axis Both Обидві
|
||||
Perspective Distort Спотворення перспективи
|
||||
Artboard Монтажна область
|
||||
Exit Вийти
|
||||
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` — це посилання на крок {r}, яке розв’язують лише `run` і `command_batch`
|
||||
@msg `ids` must be an array of object ids, not {v} `ids` має бути масивом ідентифікаторів об’єктів, а не {v}
|
||||
@msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: у результаті кроку {i} немає такого значення (щоб написати сам $, введіть $$)
|
||||
@msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: перед цим кроком немає кроку {i} (кроки рахуються з 0; щоб написати сам $, введіть $$)
|
||||
@msg object ids are non-negative integers, not {v} ідентифікатори об’єктів — невід’ємні цілі числа, а не {v}
|
||||
|
Can't render this file because it contains an unexpected character in line 632 and column 40.
|
+11
-1
@@ -167,7 +167,7 @@ The older names below remain listed because existing workflows use them.
|
||||
|---|---|
|
||||
| `command_list` | Optional `filter`, `enabled_only`; returns the command array |
|
||||
| `command_run` | `id`, optional `params`; runs the command through the existing backend |
|
||||
| `command_batch` | `steps: [{id, params?}]`, optional `stop_on_error` (default true); returns `completed`, `failed`, `results: [{ok, result\|error}]`. Each edit has its own undo step; failures set `isError` |
|
||||
| `command_batch` | `steps: [{id, params?}]`, optional `stop_on_error` (default true); returns `completed`, `failed`, `results: [{ok, result\|error}]`. Each edit has its own undo step; failures set `isError`. A param string `"$N.path"` or `"$last.path"` takes a value from an earlier step's result (see `run` below) |
|
||||
| `doc_inspect` | Optional `depth`, `childLimit`; same summary as `inspect_document` |
|
||||
| `render_preview` | Optional `max_side` (1–4096, default 1024); inline PNG of the first artboard without editing it. Artboards too large to render within the allocation bound return a tool error |
|
||||
| `ui_inspect`, `ui_screenshot` | Connected desktop state/window capture; tool errors in headless mode |
|
||||
@@ -573,6 +573,16 @@ applies to the `--cmd` just before it. `run` also accepts the host commands `fil
|
||||
`file.exportForScreens` and `tool.select`. `run --in`, `convert` and `info` read every format `document.open` reads
|
||||
(`vectorcraft-cli --help` lists them).
|
||||
|
||||
A step can use an earlier step's result, in `run` as in the `command_batch` tool. A parameter string that is exactly
|
||||
`$N` or `$N.path` takes that value from the result of step `N` (counting from 0, in the order they ran; `run --in`'s
|
||||
open isn't a step), and `$last…` from the step before. A path goes into objects by key and arrays by index:
|
||||
`"$1.id"`, `"$2.ids.0"`, `"$last.ids[0]"`. A string starting with `$$` stands for one `$` (`"$$1.99"` is `$1.99`). A
|
||||
reference that names no value fails the step, so a command never runs with one left in it:
|
||||
|
||||
```sh
|
||||
vectorcraft-cli run --cmd file.new --params '{"width":600,"height":200}' --cmd text.create --params '{"x":10,"y":60,"text":"plain and bold words","size":30}' --cmd text.setRangeStyle --params '{"id":"$1.id","start":10,"end":14,"style":"Bold"}'
|
||||
```
|
||||
|
||||
## Transparency and opacity masks
|
||||
|
||||
Transparency and opacity-mask commands take `ids` (or `id`), so they need no selection. Opacity is a percentage
|
||||
|
||||
Reference in new issue
Block a user