mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
A group's or layer's own fills and strokes paint every member's geometry, below or above the members as the new contents slot (Appearance.contents_index) says; its geometry effects reshape the members as one piece and its raster effects apply to the composite (one combined shadow), respecting knockout groups. Type's own fills and strokes paint under or over its characters. Render, SVG/PDF (via bake) and Expand Appearance evaluate them alike. Commands: appearance.* / effect.* take target: "object"|"contents" and layer ids; appearance.moveItem moves the Contents slot (from: "contents") or an item across it (contents); appearance.targetContents selects a group's members. Graphic styles applied to a group now style the group itself. The Appearance panel shows Contents/Characters rows that drag, and double-click to target the members or edit the characters' paint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
307 lines
22 KiB
Markdown
307 lines
22 KiB
Markdown
# VectorCraft MCP server
|
||
|
||
`vectorcraft-cli mcp` runs a [Model Context Protocol](https://modelcontextprotocol.io) server on stdio
|
||
(newline-delimited JSON-RPC 2.0, protocol `2025-06-18`; `2025-03-26` and `2024-11-05` also accepted). Agents use it to
|
||
draw, inspect and look at VectorCraft documents.
|
||
|
||
It has two backends:
|
||
|
||
| Mode | How | What works |
|
||
|---|---|---|
|
||
| **Remote** | `vectorcraft-cli mcp --connect 127.0.0.1:7979` (the app must run with `vectorcraft --control 7979`) | Everything. Tool calls are forwarded over the [control protocol](control-protocol.md), so you watch the app change live. |
|
||
| **Headless** | `vectorcraft-cli mcp --headless` | An in-process engine session with a CPU renderer. Everything except the UI-only tools (`inspect_ui`, `type_text`, `open_panel`, `screenshot {window:true}`). |
|
||
|
||
With no flag, the server tries `127.0.0.1:7979` and falls back to headless. Logs go to stderr; stdout carries
|
||
only protocol messages.
|
||
|
||
## Build and register
|
||
|
||
```sh
|
||
cargo build --release -p vectorcraft-cli
|
||
claude mcp add vectorcraft -- "$PWD/target/release/vectorcraft-cli" mcp
|
||
# or pin a mode:
|
||
claude mcp add vectorcraft-headless -- "$PWD/target/release/vectorcraft-cli" mcp --headless
|
||
claude mcp add vectorcraft-app -- "$PWD/target/release/vectorcraft-cli" mcp --connect 127.0.0.1:7979
|
||
```
|
||
|
||
Other clients use the same command in their JSON config:
|
||
|
||
```json
|
||
{"mcpServers": {"vectorcraft": {"command": "/abs/path/target/release/vectorcraft-cli", "args": ["mcp"]}}}
|
||
```
|
||
|
||
For a live session, start the app first: `cargo run --release -p vectorcraft -- --control 7979`.
|
||
|
||
## Tools
|
||
|
||
Coordinates are points in document space: y points down, the origin is the first artboard's top-left, and a new
|
||
document is 612 × 792 (US Letter). New objects become the selection. Most commands act on the selection or on
|
||
explicit `ids`.
|
||
|
||
Paint values (`fill`, `stroke`) accept `"#rrggbb"`, `"none"`, `[r,g,b]` (0..1), `{"c","m","y","k"}`, `{"gray"}`,
|
||
or a full `paint.setFill` params object (`{"gradient": …}`, `{"swatch": "name"}`).
|
||
The Fill/Stroke proxy commands run through `run_command`: `paint.invert` and `paint.complement` recolour the active
|
||
proxy keeping each colour's model, `paint.lastColor` / `paint.lastGradient` re-apply the last solid colour or gradient,
|
||
and `paint.recent` returns the recent colours that every paint command (and the eyedropper) feeds.
|
||
`paint.proxies` returns what the proxies show: the fill and stroke, which one is active, and whether the selected
|
||
objects' fills or strokes differ (`fillMixed` / `strokeMixed`, drawn as a "?" proxy).
|
||
|
||
| Tool | Arguments | Notes |
|
||
|---|---|---|
|
||
| `list_commands` | `{filter?, enabledOnly?}` | The command catalogue: id, label, menu, shortcut, params doc, enablement. |
|
||
| `run_command` | `{command, params?}` | Runs any command. Use it for everything without a dedicated tool. |
|
||
| `inspect_document` | `{}` | Artboards, layer tree (ids, kinds, bounds, paint), selection, history, tool. |
|
||
| `inspect_ui` | `{}` | UI state. Remote mode only. |
|
||
| `select_tool` | `{tool}` | `selection`, `directSelection`, `pen`, `rectangle`, `ellipse`, `polygon`, `star`, `lineSegment`, … |
|
||
| `pointer_gesture` | `{events:[{kind,x,y,mods?}], tool?, mods?}` | `kind` is one of `down`, `drag`, `up`, `move`, `doubleclick`. Events go through the same path as the mouse. |
|
||
| `draw_path` | `{points \| d, closed?, fill?, stroke?, strokeWidth?}` | `points` is `[[x,y],…]` or `[{x,y,in?,out?,smooth?},…]`. `d` is SVG path data. |
|
||
| `draw_shape` | `{shape, …geometry, fill?, stroke?, strokeWidth?}` | `rectangle`/`ellipse`: `x,y,width,height` (plus `radius` for corners). `polygon`: `cx,cy,radius,sides`. `star`: `cx,cy,radius1,radius2,points`. `line`: `x1,y1,x2,y2`. |
|
||
| `set_paint` | `{fill?, stroke?, strokeWidth?, ids?}` | Applies to the selection (or `ids`) and becomes the default for new art. |
|
||
| `press_key` | `{key, mods?}` | Remote: a real key event. Headless: runs the command or tool bound to that shortcut, or sends the key to the busy tool. |
|
||
| `type_text` | `{text}` | Remote only. |
|
||
| `invoke_menu` | `{command, params?}` | Invokes a menu item by command id. Includes UI commands such as `view.*` and `window.*` in remote mode. |
|
||
| `open_panel` | `{panel}` | Remote only. |
|
||
| `screenshot` | `{path?, scale?, artboard?, window?}` | Returns MCP image content (`image/png`, base64) plus a text block. Renders the artboard; `window:true` captures the app window (remote only). |
|
||
| `open_file` | `{path}` | Opens any readable file as a new active document: `.vectorcraft`/`.drawcraft`, `.svg`/`.svgz`, `.pdf`/`.ai`, `.ait`, PNG, JPEG, GIF, WebP, TIFF, BMP (an image opens as a document of its pixel size). Templates open as a new untitled document. `run_command document.formats` lists the formats. |
|
||
| `save_file` | `{path?}` | Saves in the native `.vectorcraft` format. |
|
||
| `export` | `{path?, format?, scale?, artboard?, range?, selection?, outlineText?, options?}` | `svg`, `pdf`, `png`, `jpg`, `webp` or `vectorcraft` (the list comes from `document.formats`). When `format` is omitted, it comes from the path's extension. PDF writes one page per artboard: all of them, or `artboard` (0-based) / `range` (`"1-3, 5"`, 1-based); the other formats write one artboard. `options` carries more format options (e.g. `{"quality": 80}` for JPEG). `selection: true` exports the selected objects cropped to their bounds; `outlineText: true` writes SVG text as paths. Template layers are left out, live effects are kept, and exporting `vectorcraft` never changes the document's path. Without `path` the bytes come back as `dataBase64`. Both backends run the same `document.export` call. |
|
||
| `add_text` | `{text, x?, y?, width?, height?, path?, mode?, pathEffect?, size?, font?, color?}` | Point type at (x, y); area type with `width`/`height`; or `path` + `mode` (`area`/`onPath`) to flow text in or along a path, with `pathEffect` (`rainbow`, `skew`, `3dRibbon`, `stairStep`, `gravity`). |
|
||
| `apply_effect` | `{effect?, params?, ids?}` | Appends a live effect. Without `effect`, returns the effect catalogue with parameters and defaults. |
|
||
| `pathfinder` | `{operation, ids?}` | `unite`, `minusFront`, `intersect`, `exclude`, `divide`, `trim`, `merge`, `crop`, `outline`, `minusBack`. For a live version, apply the `pathfinder.*` effect to a group. |
|
||
| `transform` | `{ids?, dx?, dy?, rotate?, scale?, scaleX?, scaleY?, reflect?, shear?, origin?, copy?}` | Runs move, rotate, scale, reflect, shear in that order. With `copy`, the first step duplicates. |
|
||
| `create_graph` | `{type?, x, y, width, height, csv? \| series?, categories?, rows?}` | The nine Illustrator graph types: column, stacked column, bar, stacked bar, line, area, scatter, pie and radar. Edit later with `graph.setData` / `graph.setType` via `run_command`. |
|
||
| `text_wrap` | `{ids?, offset?, invert?, release?}` | Area type below the objects (same layer) flows around them. |
|
||
| `undo` / `redo` | `{}` | |
|
||
|
||
Appearance stacks: an object can carry several fills and strokes (`appearance.addFill`, `appearance.addStroke`), indexed
|
||
in paint order (0 is painted first, the bottom row of the Appearance panel). `paint.setFill`, `paint.setStroke`,
|
||
`stroke.set`, `stroke.setAdvanced`, `paint.editGradient`, `paint.setGradientGeom` and `transparency.set` take
|
||
`item` to edit one of them; `run_command appearance.setActiveItem {"index": n}` makes that row the target of later
|
||
calls that omit `item` (as clicking the row in the Appearance panel does) until the selection changes; the proxy of
|
||
its kind (`paint.proxies`) and the Gradient tool's annotator then show that row.
|
||
`inspect_document` reports it as `paint.appearanceItem`. Live effects take the same `item` to apply to one fill or
|
||
stroke instead of the whole object: `run_command effect.apply {"effect": "path.offsetPath", "item": 0}`, and
|
||
`effect.remove`, `effect.setParams` (`visible` toggles one) and `effect.duplicate` address that item's effects;
|
||
`apply_effect` uses the active item. `effect.list` reports each object's item effects under `applied[].items`.
|
||
|
||
Errors (unknown tool, bad arguments, a disabled or failing command) come back as a normal result with
|
||
`isError: true` and a message. The model can read the message and retry.
|
||
|
||
Swatches go through `run_command`: `swatch.list` lists every swatch with its colour group, kind and colour;
|
||
`swatch.edit {name, newName?, color?, mode?, global?, spot?}` edits one (fills, strokes and text linked to a
|
||
global or spot swatch follow its colour and name, as one undo step); `swatch.delete {names, unlink?}` deletes
|
||
swatches and colour groups in one undo step (art using a deleted global swatch keeps its colour, unlinked).
|
||
`swatch.new {color?, mode?, spot?, global?, group?}` saves a colour (into a colour group with `group`), gradient
|
||
or pattern; `swatch.newGroup {fromArtwork: true, toGlobal?, includeTints?}` makes a colour group of the selected
|
||
art's colours (by default as global swatches the art links to).
|
||
`swatch.move {names, to?, group?}` reorders swatches or moves them into or out of a colour group (as dragging
|
||
them in the Swatches panel does); give colour group names instead to reorder the groups. Dropping a swatch on art
|
||
in the app runs `paint.setFill` (or `paint.setStroke`, the active proxy) with `ids: [the object under the pointer]`.
|
||
The Swatches panel menu's commands: `swatch.addUsedColors {selection?, global?}`, `swatch.unused` (a query: the
|
||
names Select All Unused selects), `swatch.merge {names}` (the first is kept), `swatch.ungroup {name}` and
|
||
`swatch.sortByKind`.
|
||
Document colour mode: `file.new {colorMode: "cmyk"}` starts a CMYK document with CMYK default swatches, and RGB
|
||
colours applied to it (`paint.setFill`/`setStroke` colours and gradient stops, `swatch.new`) are stored as CMYK;
|
||
Gray stays Gray, and `keepModel: true` keeps a colour as given. RGB documents keep colours as given. Harmonies, Edit
|
||
Colors blends, inversions and Recolor Artwork keep each colour's model (a blend between models takes the document's).
|
||
`swatch.new {colors: [...]}` saves several colours as swatches in one undo step. `color.harmony {color, rule, steps?, variation?, amount?}` answers what the Color
|
||
Guide panel shows: the harmony rule's colours, base first, and per colour its row of `2·steps+1` variations
|
||
(shades, cool or muted on the left, tints, warm or vivid on the right).
|
||
|
||
## Resources
|
||
|
||
| URI | Content |
|
||
|---|---|
|
||
| `vectorcraft://document` | `document.inspect` summary (JSON) |
|
||
| `vectorcraft://document/json` | The complete document model (JSON) |
|
||
|
||
## Examples
|
||
|
||
Draw, look, export:
|
||
|
||
```json
|
||
{"name":"draw_shape","arguments":{"shape":"rectangle","x":72,"y":72,"width":200,"height":120,"radius":12,"fill":"#1e88e5","stroke":"none"}}
|
||
{"name":"draw_shape","arguments":{"shape":"star","cx":400,"cy":300,"radius1":90,"radius2":40,"points":5,"fill":"#ffc107","stroke":"#000000","strokeWidth":2}}
|
||
{"name":"draw_path","arguments":{"d":"M72 400 C 150 300 250 500 330 400","stroke":"#e53935","strokeWidth":4,"fill":"none"}}
|
||
{"name":"screenshot","arguments":{"scale":0.5}}
|
||
{"name":"export","arguments":{"path":"/tmp/art.svg"}}
|
||
```
|
||
|
||
Long-tail commands:
|
||
|
||
```json
|
||
{"name":"list_commands","arguments":{"filter":"align"}}
|
||
{"name":"run_command","arguments":{"command":"select.all"}}
|
||
{"name":"run_command","arguments":{"command":"object.align","params":{"align":"left"}}}
|
||
{"name":"run_command","arguments":{"command":"object.group"}}
|
||
```
|
||
|
||
Drive a tool like a mouse:
|
||
|
||
```json
|
||
{"name":"pointer_gesture","arguments":{"tool":"ellipse","events":[
|
||
{"kind":"down","x":100,"y":100},{"kind":"drag","x":150,"y":140},{"kind":"up","x":200,"y":180}]}}
|
||
```
|
||
|
||
Edit a gradient with the Gradient tool's annotator (the bar runs along the vector; stop chips sit 10 px under
|
||
it, midpoint diamonds 7 px above): a click on the bar adds a stop, dragging a chip moves it (drag it off the bar to
|
||
delete it, hold Alt to copy it), dragging the end handle changes the vector. The selected stop
|
||
(`gradient.selectStop`) takes Delete and ←/→:
|
||
|
||
```json
|
||
{"name":"run_command","arguments":{"command":"paint.setFill","params":{"gradient":{"start":[100,150],"end":[200,150]}}}}
|
||
{"name":"pointer_gesture","arguments":{"tool":"gradient","events":[{"kind":"down","x":130,"y":150},{"kind":"up","x":130,"y":150}]}}
|
||
{"name":"pointer_gesture","arguments":{"events":[{"kind":"down","x":130,"y":160},{"kind":"drag","x":160,"y":160},{"kind":"up","x":160,"y":160}]}}
|
||
{"name":"press_key","arguments":{"key":"Delete"}}
|
||
```
|
||
|
||
Raw protocol (for debugging):
|
||
|
||
```sh
|
||
printf '%s\n' \
|
||
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"sh","version":"0"}}}' \
|
||
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
|
||
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"draw_shape","arguments":{"shape":"ellipse","x":10,"y":10,"width":100,"height":80,"fill":"#ff0000"}}}' \
|
||
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"inspect_document","arguments":{}}}' \
|
||
| target/release/vectorcraft-cli mcp --headless
|
||
```
|
||
|
||
## Headless batch CLI
|
||
|
||
```sh
|
||
vectorcraft-cli commands # command catalogue (JSON)
|
||
vectorcraft-cli run --in in.svg \
|
||
--cmd select.all --cmd paint.setFill --params '{"color":"#ff0000"}' \
|
||
--export out.svg --export out.png --scale 2
|
||
vectorcraft-cli run --cmd file.new --params '{"width":800,"height":600}' \
|
||
--cmd shape.star --params '{"cx":400,"cy":300,"radius1":200,"radius2":90}' \
|
||
--export star.vectorcraft
|
||
```
|
||
|
||
`run` prints one JSON line per step (`open`, `cmd`, `export`) and exits non-zero on the first failure. `--params`
|
||
applies to the `--cmd` just before it. `run` also accepts the host commands `file.open`, `file.save`, `file.export`,
|
||
`file.exportForScreens` and `tool.select`. `run --in`, `convert` and `info` read every format `document.open` reads
|
||
(`vectorcraft-cli --help` lists them).
|
||
|
||
## Transparency and opacity masks
|
||
|
||
Transparency and opacity-mask commands take `ids` (or `id`), so they need no selection. Opacity is a percentage
|
||
(0..100) in every command (`transparency.set`, `object.setProps`, `appearance.setItem`). `transparency.info` returns
|
||
the Transparency panel's values, with `null` where the objects differ. Making a mask from one object gives it an
|
||
empty mask and enters mask editing: art drawn then becomes the mask, and the mask commands act on the masked object
|
||
until `transparency.stopEditingOpacityMask`. Saves and exports never include the editing layer.
|
||
|
||
```json
|
||
{"name":"run_command","arguments":{"command":"transparency.set","params":{"ids":[12],"opacity":40,"blend":"Multiply"}}}
|
||
{"name":"run_command","arguments":{"command":"transparency.makeOpacityMask","params":{"ids":[12,15],"invert":true}}}
|
||
{"name":"run_command","arguments":{"command":"transparency.setOpacityMask","params":{"id":12,"clip":false}}}
|
||
{"name":"run_command","arguments":{"command":"transparency.info","params":{"ids":[12,20]}}}
|
||
```
|
||
|
||
Knockout Group has three states: `transparency.set {knockout: "on"|"off"|"neutral"}` (`true` = on, `false` =
|
||
neutral, the default). In a knockout group each child hides what it covers of the children below it; neutral groups
|
||
pass the enclosing group's setting to their children, off groups never knock out. `knockoutShape: true` makes the
|
||
object's opacity and opacity mask scale how much it knocks out. `transparency.togglePageKnockoutGroup` and
|
||
`transparency.togglePageIsolatedBlending` (`{value?}`) treat the whole page as a knockout or isolated group; they are
|
||
saved with the document and undoable, and `transparency.info` reports them. PDF and SVG write knockout groups as
|
||
soft-masked groups with the same look (the PDF export reports a warning).
|
||
|
||
Groups are not isolated unless Isolate Blending is on: a blend mode inside a group with opacity, a blend mode, an
|
||
opacity mask, knockout or a clip reaches the art below the group, on screen and in PDF (a knockout group's elements
|
||
then composite against the art below it). SVG has no non-isolated groups, so there such groups stay isolated.
|
||
|
||
## Clipping masks
|
||
|
||
`object.clippingMask.make` clips the selected objects by the topmost one, which may be a path, a compound path or a
|
||
text object (it loses its paint). Compound holes, even-odd fills, glyph outlines and the union of a group's members
|
||
clip alike on screen, in raster export and in SVG and PDF. `object.clippingMask.release` turns the clip group into a
|
||
plain group and keeps the clipping path, unpainted.
|
||
|
||
```json
|
||
{"name":"run_command","arguments":{"command":"select.set","params":{"ids":[12,15]}}}
|
||
{"name":"run_command","arguments":{"command":"object.clippingMask.make","params":{}}}
|
||
```
|
||
|
||
`layer.clippingMask.toggle {id?}` is the Layers panel's clipping mask button: the top object of the layer `id` (default:
|
||
the one selected group, else the current layer) becomes its clipping path (unpainted, moved to the bottom of the layer,
|
||
so art added later is clipped too); called again it releases the mask. It returns `{clip}`, and the Layers panel
|
||
underlines clipping-path names.
|
||
|
||
## Graphic styles
|
||
|
||
A graphic style holds an appearance (fills, strokes, effects) plus opacity, blend mode, isolate and knockout.
|
||
`graphicStyle.new {name?, id?}` captures an object (a group without its own fills or strokes lends its topmost
|
||
object's, type its characters'). `graphicStyle.apply {name, ids?}` gives the objects the style and links them;
|
||
`add: true` adds the style on top of the existing appearance instead. Linked objects stay linked while they keep the
|
||
style's look: editing their appearance or transparency breaks the link, and `graphicStyle.redefine {name?, id?}`
|
||
updates only the objects still linked. `graphicStyle.list` returns each style with the ids linked to it and the
|
||
style of the first selected object; `select.same.graphicStyle` selects an object's fellow users.
|
||
Styles keep placed gradients relative to the bounds of the object they were made from, and each object a
|
||
style is applied to gets them at the same place relative to its own bounds.
|
||
|
||
```json
|
||
{"name":"run_command","arguments":{"command":"graphicStyle.new","params":{"id":12,"name":"Glow"}}}
|
||
{"name":"run_command","arguments":{"command":"graphicStyle.apply","params":{"name":"Glow","ids":[15,20]}}}
|
||
{"name":"run_command","arguments":{"command":"graphicStyle.redefine","params":{"id":12}}}
|
||
{"name":"run_command","arguments":{"command":"graphicStyle.list","params":{}}}
|
||
```
|
||
|
||
## Editing appearance stacks
|
||
|
||
`effect.move {from, to, fromItem?, toItem?, copy?}` reorders an effect or moves it between the object's effects
|
||
(`null`) and a fill's or stroke's (an item index), as dragging its row in the Appearance panel does; `copy: true` copies
|
||
it (Alt-drag). `appearance.duplicateItem {index, to?}` / `{indices}` and `appearance.removeItem {index | indices}`
|
||
act on several fills/strokes at once, and `appearance.showAllHidden` makes every hidden fill, stroke and effect
|
||
visible again. In remote mode, `effect.dialog {effect, index, item}` opens an applied effect's dialog prefilled
|
||
(`ui.dialog.confirm` runs `effect.setParams`); see the control protocol for the "already applied" question.
|
||
|
||
## Overprint
|
||
|
||
Each fill and stroke (and each character's fill and stroke) can overprint: its inks print over the inks below instead
|
||
of knocking them out. `object.setOverprint {fill?, stroke?, item?, ids?}` sets it (groups set their contents, type its
|
||
characters too; `item` aims at one appearance item) and `attributes.info {ids?}` reads it back (`null` where the
|
||
objects differ). Overprint Preview (`view.overprintPreview`) and Separations Preview show it; rendering approximates
|
||
it by multiplying. Older files that listed Overprint Black objects in the document get the flags on load.
|
||
|
||
```json
|
||
{"name":"run_command","arguments":{"command":"object.setOverprint","params":{"ids":[12],"stroke":true}}}
|
||
{"name":"run_command","arguments":{"command":"attributes.info","params":{"ids":[12]}}}
|
||
{"name":"run_command","arguments":{"command":"view.overprintPreview","params":{"on":true}}}
|
||
```
|
||
|
||
## Edit Colors and Recolor Artwork
|
||
|
||
`edit.colors.invert`, `edit.colors.toCMYK`, `edit.colors.toGrayscale`, `edit.colors.toRGB`, `edit.colors.saturate`,
|
||
`edit.colors.adjustBalance` and `recolor.apply` recolour everything inside the selection (or `ids`) in one undo step:
|
||
fills, strokes, text, gradient stops, gradient-mesh points, embedded images (a recoloured copy of the image; linked
|
||
images are left alone) and the tiles of pattern fills and strokes (a recoloured copy saved as a new pattern swatch,
|
||
e.g. "Dots 2"; the original pattern is untouched). `includeImages: false` / `includePatterns: false` leave images or
|
||
patterns out. The Blend commands also grade gradient meshes, which keep their shading.
|
||
|
||
```json
|
||
{"name":"run_command","arguments":{"command":"recolor.colors","params":{}}}
|
||
{"name":"run_command","arguments":{"command":"recolor.apply","params":{"map":{"#ff0000":"#0055ff"},"includeImages":false}}}
|
||
{"name":"run_command","arguments":{"command":"edit.colors.adjustBalance","params":{"mode":"cmyk","m":-20,"k":10}}}
|
||
```
|
||
|
||
## Group, layer and type appearance
|
||
|
||
Groups and layers carry fills, strokes and effects of their own, as in the reference app: their fills and strokes
|
||
paint every member's geometry, and their effects apply to the members as one piece (one combined drop shadow; a
|
||
Transform or Warp moves or bends the whole group). Their Contents row (type: Characters) is a slot in the stack:
|
||
fills and strokes above it paint over the members (characters), those below under them; new items go above it.
|
||
`appearance.moveItem {"from": "contents", "to": n}` puts the row above the bottom `n` items, and an item move takes
|
||
`contents` to say where the row ends up in the same undo step; `document.node` shows the slot as
|
||
`appearance.contents_index`. The `appearance.*`, `effect.*` and `graphicStyle.apply` commands edit the selected
|
||
objects themselves (a group's own stack; layers through `ids`) or, with `target: "contents"`, the objects inside the
|
||
groups and layers. `appearance.targetContents` selects a group's members (double-clicking the Contents row).
|
||
SVG and PDF export bake a group's own fills, strokes and geometry effects into paths; its raster effects stay a
|
||
filter on the whole group.
|
||
|
||
```json
|
||
{"name":"run_command","arguments":{"command":"appearance.addFill","params":{"ids":[7]}}}
|
||
{"name":"run_command","arguments":{"command":"appearance.moveItem","params":{"ids":[7],"from":"contents","to":1}}}
|
||
{"name":"run_command","arguments":{"command":"effect.apply","params":{"ids":[2],"effect":"stylize.dropShadow"}}}
|
||
{"name":"run_command","arguments":{"command":"appearance.addStroke","params":{"ids":[7],"target":"contents"}}}
|
||
```
|