@pixerate/editor 0.222.0 → 0.222.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/README.md +11 -3
- package/skills/pixerate-paint-editor/SKILL.md +20 -59
- package/skills/pixerate-paint-editor/references/oils.md +19 -0
- package/skills/pixerate-paint-editor/references/wet-media.md +35 -0
- package/skills/pixerate-paint-oil-impasto/SKILL.md +103 -0
- package/skills/pixerate-paint-oil-impasto/evals/impasto-highlights.json +24 -0
- package/skills/pixerate-paint-oil-impasto/evals/lean-to-thick.json +17 -0
- package/skills/pixerate-paint-oil-impasto/evals/mix-green.json +15 -0
- package/skills/pixerate-paint-sketch-to-finish/SKILL.md +97 -0
- package/skills/pixerate-paint-sketch-to-finish/evals/clipped-shading.json +26 -0
- package/skills/pixerate-paint-sketch-to-finish/evals/flats-under-ink.json +27 -0
- package/skills/pixerate-paint-sketch-to-finish/evals/sketch-ink-hide.json +20 -0
- package/skills/pixerate-paint-watercolour/SKILL.md +106 -0
- package/skills/pixerate-paint-watercolour/evals/glaze-over-dry.json +24 -0
- package/skills/pixerate-paint-watercolour/evals/paper-and-brush-for-detail.json +16 -0
- package/skills/pixerate-paint-watercolour/evals/sky-wash.json +19 -0
- package/skills/pixerate-vector/SKILL.md +11 -61
- package/skills/pixerate-vector/references/best-practices.md +62 -0
- package/skills/pixerate-vector-icon-set/SKILL.md +111 -0
- package/skills/pixerate-vector-icon-set/evals/consistent-strokes.json +22 -0
- package/skills/pixerate-vector-icon-set/evals/plus-icon.json +15 -0
- package/skills/pixerate-vector-icon-set/evals/repeats-as-symbol.json +192 -0
- package/skills/pixerate-vector-illustration/SKILL.md +112 -0
- package/skills/pixerate-vector-illustration/evals/clip-stripes.json +21 -0
- package/skills/pixerate-vector-illustration/evals/radial-sun.json +16 -0
- package/skills/pixerate-vector-illustration/evals/subtle-shadow.json +16 -0
- package/skills/pixerate-vector-logo/SKILL.md +107 -0
- package/skills/pixerate-vector-logo/evals/centre-lockup.json +20 -0
- package/skills/pixerate-vector-logo/evals/knockout-ring.json +21 -0
- package/skills/pixerate-vector-logo/evals/live-union.json +19 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-paint-watercolour
|
|
3
|
+
description: The watercolour workflow in the Pixerate paint editor - choosing the paper, planning light to dark and keeping the paper's white for highlights, wet-in-wet and wet-on-dry washes, letting water flow and drying before glazing, steering water with the wet tools, granulation, blooms and edge darkening, and finishing detail with a small brush. Use when asked to paint in watercolour or gouache, lay a wash, paint a sky, glaze, paint wet-in-wet, make blooms or granulation, or paint a loose or botanical watercolour, or when wet_paper, advance_wet_paint, dry_paint or set_wet_settings are available. Load with pixerate-paint-editor, which lists every tool.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-paint-editor
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Painting in watercolour
|
|
10
|
+
|
|
11
|
+
Watercolour is transparent and it moves. Each wash lays water and pigment that keep flowing
|
|
12
|
+
until they dry: edges darken, pigment settles into the paper's valleys, wet colours bleed into
|
|
13
|
+
each other. Light comes from the paper, so you work light to dark and keep the white of the paper
|
|
14
|
+
where you want highlights. A dried wash is fixed; the next one glazes over it. For an agent,
|
|
15
|
+
time only passes when you call `advance_wet_paint` or `dry_paint`.
|
|
16
|
+
|
|
17
|
+
This skill is the method. `pixerate-paint-editor` has the full tool reference and
|
|
18
|
+
`references/wet-media.md` the engine's detail.
|
|
19
|
+
|
|
20
|
+
## Goal and quality bar
|
|
21
|
+
|
|
22
|
+
- The paper suits the piece: cold pressed for most work, rough for texture and granulation, hot
|
|
23
|
+
pressed for fine detail (`get_canvas_info` `paper.preset`).
|
|
24
|
+
- Washes are even where they should be and soft where they should be; colours glow because
|
|
25
|
+
they are layered, not mixed to mud.
|
|
26
|
+
- Highlights are the paper itself, not white paint (unless the user asks for gouache).
|
|
27
|
+
- Every wash is dry before the next glaze over it (`get_wet_paint` `wet: false`), and the
|
|
28
|
+
painting is dry before export.
|
|
29
|
+
|
|
30
|
+
## Inputs to confirm
|
|
31
|
+
|
|
32
|
+
- **Subject and look:** loose and atmospheric (wet-in-wet) or crisp and detailed (wet-on-dry).
|
|
33
|
+
- **Palette:** a few colours; `color_harmony` or `mix_colors` to plan.
|
|
34
|
+
- **Highlights to keep white**, and where they are.
|
|
35
|
+
|
|
36
|
+
## Working method
|
|
37
|
+
|
|
38
|
+
1. **Ground.** `set_paper` with a watercolour preset (`cold-pressed`, `rough-watercolor`,
|
|
39
|
+
`hot-pressed`). Keep each pass on its own layer when you may want to change it (`add_layer`).
|
|
40
|
+
**Check:** `get_canvas_info` shows the paper.
|
|
41
|
+
2. **Plan values light to dark** and where the paper stays white. Mix colours first
|
|
42
|
+
(`mix_colors` predicts the pigment mix; `add_swatch` keeps them).
|
|
43
|
+
3. **First wash, lightest colour.** For soft skies and backgrounds, `wet_paper` the area first
|
|
44
|
+
(wet-in-wet); for crisp edges paint on dry paper. `set_brush` `watercolor-wash`, `set_color`,
|
|
45
|
+
then a few long `paint_stroke`s across the area, overlapping, avoiding the highlights.
|
|
46
|
+
**Check:** `get_wet_paint` shows the layer wet; `pick_color` inside is the colour you meant.
|
|
47
|
+
4. **Let it move, then dry.** `advance_wet_paint` (`seconds` 1-5) to let it spread, bloom and
|
|
48
|
+
granulate; drop a second colour in while it is wet for soft blends. Then `dry_paint`.
|
|
49
|
+
**Check:** `get_wet_paint` `wet: false`.
|
|
50
|
+
5. **Glaze.** Each further wash goes over a dry one, a little darker or another hue (blue over
|
|
51
|
+
yellow makes green). Paint only where it should deepen.
|
|
52
|
+
**Check:** `pick_color` where you glazed is darker; where you did not, unchanged.
|
|
53
|
+
6. **Shapes, then details.** `watercolor-round` for shapes, `watercolor-detail` for lines and
|
|
54
|
+
details on dry paper, last.
|
|
55
|
+
7. **Steer and fix while wet**: `blot` lifts paint (clouds, a highlight you lost), the
|
|
56
|
+
`add-water` tool softens an edge, `hair-dryer` stops a spread, `set_canvas_tilt` makes a wash
|
|
57
|
+
run, `set_wet_settings` tunes `granulation`, `edgeDarkening` and `bloom` for the layer.
|
|
58
|
+
8. **Finish.** `dry_paint` everything; `render_preview` to look; export.
|
|
59
|
+
|
|
60
|
+
## Decision table
|
|
61
|
+
|
|
62
|
+
| Want | Approach |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| Soft sky, misty background | `wet_paper`, then `watercolor-wash`; drop colours in while wet |
|
|
65
|
+
| Crisp shapes, hard edges with a dark rim | Paint on dry paper |
|
|
66
|
+
| Deeper colour without mud | Dry, then glaze a second wash |
|
|
67
|
+
| A white highlight | Leave the paper; or `blot` while wet to lift |
|
|
68
|
+
| Light over dark | `gouache` (body 1) |
|
|
69
|
+
| Grainy, textured washes | `rough-watercolor` paper and `granulation` up in `set_wet_settings` |
|
|
70
|
+
| Fine lines and detail | `hot-pressed` paper, `watercolor-detail`, on dry paper |
|
|
71
|
+
| Cauliflower blooms | Add water into a drying wash; `bloom` up |
|
|
72
|
+
|
|
73
|
+
## Recipes
|
|
74
|
+
|
|
75
|
+
**Flat wash.** `set_brush` `watercolor-wash` → `set_color` → one `paint_stroke` zigzagging
|
|
76
|
+
across the area (rows about half the brush size apart) → `advance_wet_paint` `seconds` 2-5 →
|
|
77
|
+
`dry_paint`.
|
|
78
|
+
|
|
79
|
+
**Glaze.** On the dried wash, the same or a darker colour, painted only where it should deepen
|
|
80
|
+
→ `dry_paint`. Repeat for more depth; three glazes are usually plenty.
|
|
81
|
+
|
|
82
|
+
**Wet-in-wet sky.** `wet_paper` over the sky → blue wash along the top → while wet, a little
|
|
83
|
+
warm colour near the horizon → `advance_wet_paint` → `blot` a few clouds → `dry_paint`.
|
|
84
|
+
|
|
85
|
+
## Pitfalls
|
|
86
|
+
|
|
87
|
+
- **Glazing on wet paint** mixes the two washes instead of layering them; dry first.
|
|
88
|
+
- **Painting light over dark** does not work in transparent watercolour; plan the whites.
|
|
89
|
+
- **Overworking**: many strokes over a wet wash lift and muddy it; lay it and leave it.
|
|
90
|
+
- **Drawing-paper presets** (cartridge, smooth) do not behave like watercolour paper.
|
|
91
|
+
- **Oil and watercolour on one layer**: a layer keeps the first medium's wet settings; use
|
|
92
|
+
separate layers.
|
|
93
|
+
- **Nothing moves by itself** for an agent: without `advance_wet_paint` there is no flow, and
|
|
94
|
+
without `dry_paint` the next stroke paints into wet paint.
|
|
95
|
+
|
|
96
|
+
## Tools used
|
|
97
|
+
|
|
98
|
+
- `get_canvas_info`, `list_layers`, `render_preview`, `pick_color`: read the canvas and check colours
|
|
99
|
+
- `list_papers`, `set_paper`: the watercolour ground
|
|
100
|
+
- `add_layer`, `update_layer`: a layer per pass
|
|
101
|
+
- `set_brush`, `set_color`, `mix_colors`, `color_harmony`, `add_swatch`: brush and palette
|
|
102
|
+
- `paint_stroke`: washes, shapes and details, and the wet tools (water, dryer, blot)
|
|
103
|
+
- `wet_paper`, `advance_wet_paint`, `dry_paint`, `get_wet_paint`: wet-in-wet, flow, drying and checking
|
|
104
|
+
- `set_wet_settings`, `set_canvas_tilt`, `set_flow_map`: tune and steer the water
|
|
105
|
+
- `undo_paint`: take back a step
|
|
106
|
+
- `export_image`: deliver
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "The blue wash on this sheet is dry. Glaze a second wash of the same blue over the left half to deepen it, without lifting or muddying the first one.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "paint",
|
|
5
|
+
"width": 64,
|
|
6
|
+
"height": 64,
|
|
7
|
+
"setup": [
|
|
8
|
+
{ "tool": "set_brush", "args": { "id": "watercolor-wash" } },
|
|
9
|
+
{ "tool": "set_color", "args": { "color": "#6fa8dc" } },
|
|
10
|
+
{ "tool": "paint_stroke", "args": { "points": [{ "x": 0, "y": 10 }, { "x": 64, "y": 10 }, { "x": 64, "y": 32 }, { "x": 0, "y": 32 }, { "x": 0, "y": 54 }, { "x": 64, "y": 54 }] } },
|
|
11
|
+
{ "tool": "dry_paint" }
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"checks": [
|
|
15
|
+
{ "why": "a second watercolour wash", "tool": "list_strokes", "expect": { "total": 2, "strokes": { "$every": { "brushId": { "$in": ["watercolor-round", "watercolor-wash", "gradient-wash", "watercolor-detail"] } } } } },
|
|
16
|
+
{ "why": "the left half is deeper (darker) than the paper-side right", "tool": "pick_color", "args": { "x": 10, "y": 32 }, "expect": { "color": { "$expr": "(([r, g, b]) => 0.3 * r + 0.59 * g + 0.11 * b < 170 && b > r + 15)([1, 3, 5].map((i) => parseInt(v.slice(i, i + 2), 16)))" } } },
|
|
17
|
+
{ "why": "the glaze is dry, so it stays put", "tool": "get_wet_paint", "expect": { "wet": false } }
|
|
18
|
+
],
|
|
19
|
+
"solution": [
|
|
20
|
+
{ "tool": "get_wet_paint" },
|
|
21
|
+
{ "tool": "paint_stroke", "args": { "points": [{ "x": 2, "y": 12 }, { "x": 26, "y": 12 }, { "x": 26, "y": 32 }, { "x": 2, "y": 32 }, { "x": 2, "y": 52 }, { "x": 26, "y": 52 }] } },
|
|
22
|
+
{ "tool": "dry_paint" }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "I want a detailed botanical study in watercolour with crisp, fine lines. Set up the paper for that and paint the thin stem with a small detail brush, a green line from (32, 60) up to (32, 20).",
|
|
3
|
+
"fixture": { "editor": "paint", "width": 64, "height": 64 },
|
|
4
|
+
"checks": [
|
|
5
|
+
{ "why": "smooth hot-pressed paper for fine detail", "tool": "get_canvas_info", "expect": { "paper": { "preset": "hot-pressed" } } },
|
|
6
|
+
{ "why": "a fine watercolour detail brush", "tool": "list_strokes", "expect": { "strokes": { "$some": { "brushId": "watercolor-detail" } } } },
|
|
7
|
+
{ "why": "a green stem", "tool": "pick_color", "args": { "x": 32, "y": 40 }, "expect": { "color": { "$expr": "(([r, g, b]) => g > r + 10 && g > b)([1, 3, 5].map((i) => parseInt(v.slice(i, i + 2), 16)))" } } }
|
|
8
|
+
],
|
|
9
|
+
"solution": [
|
|
10
|
+
{ "tool": "set_paper", "args": { "preset": "hot-pressed" } },
|
|
11
|
+
{ "tool": "set_brush", "args": { "id": "watercolor-detail" } },
|
|
12
|
+
{ "tool": "set_color", "args": { "color": "#3f7d3a" } },
|
|
13
|
+
{ "tool": "paint_stroke", "args": { "points": [{ "x": 32, "y": 60 }, { "x": 32, "y": 40 }, { "x": 32, "y": 20 }] } },
|
|
14
|
+
{ "tool": "dry_paint" }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Paint a flat light-blue sky wash across the top third of the sheet in watercolour, and let it dry before anything else goes on.",
|
|
3
|
+
"fixture": { "editor": "paint", "width": 64, "height": 64, "setup": [{ "tool": "set_paper", "args": { "preset": "cartridge" } }] },
|
|
4
|
+
"checks": [
|
|
5
|
+
{ "why": "on watercolour paper", "tool": "get_canvas_info", "expect": { "paper": { "preset": { "$in": ["cold-pressed", "hot-pressed", "rough-watercolor"] } } } },
|
|
6
|
+
{ "why": "painted with a watercolour brush", "tool": "list_strokes", "expect": { "strokes": { "$length": { "$min": 1 }, "$every": { "brushId": { "$in": ["watercolor-round", "watercolor-wash", "gradient-wash", "watercolor-detail"] } } } } },
|
|
7
|
+
{ "why": "the sky is blue", "tool": "pick_color", "args": { "x": 32, "y": 6 }, "expect": { "color": { "$expr": "(([r, g, b]) => b > r + 15)([1, 3, 5].map((i) => parseInt(v.slice(i, i + 2), 16)))" } } },
|
|
8
|
+
{ "why": "the wash is dry", "tool": "get_wet_paint", "expect": { "wet": false } }
|
|
9
|
+
],
|
|
10
|
+
"solution": [
|
|
11
|
+
{ "tool": "set_paper", "args": { "preset": "cold-pressed" } },
|
|
12
|
+
{ "tool": "set_brush", "args": { "id": "watercolor-wash" } },
|
|
13
|
+
{ "tool": "set_color", "args": { "color": "#6fa8dc" } },
|
|
14
|
+
{ "tool": "paint_stroke", "args": { "points": [{ "x": 0, "y": 6 }, { "x": 64, "y": 6 }, { "x": 64, "y": 14 }, { "x": 0, "y": 14 }] } },
|
|
15
|
+
{ "tool": "advance_wet_paint", "args": { "seconds": 30 } },
|
|
16
|
+
{ "tool": "dry_paint" },
|
|
17
|
+
{ "tool": "get_wet_paint" }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -54,6 +54,15 @@ carry a live effect stack. Every command is a `vector.*` command run with `run_c
|
|
|
54
54
|
Parameter details (gradient fields, effect parameters, width profiles, envelopes) are in
|
|
55
55
|
[references/commands.md](references/commands.md).
|
|
56
56
|
|
|
57
|
+
## Workflow skills
|
|
58
|
+
|
|
59
|
+
For these jobs, load the workflow skill too: it has the method, the checks between steps and
|
|
60
|
+
the pitfalls, and uses the commands below.
|
|
61
|
+
|
|
62
|
+
- `pixerate-vector-icon-set`: icons on one grid, one stroke and style, crisp, as symbols and SVG.
|
|
63
|
+
- `pixerate-vector-logo`: marks from booleans, a lockup, colour variants, outlined delivery files.
|
|
64
|
+
- `pixerate-vector-illustration`: block in, gradients and stacked fills, masks, blends, shadows.
|
|
65
|
+
|
|
57
66
|
## Working method
|
|
58
67
|
|
|
59
68
|
1. **Read.** `vector.list` gives the artboard size, every node depth first (id, type, name,
|
|
@@ -74,30 +83,16 @@ Parameter details (gradient fields, effect parameters, width profiles, envelopes
|
|
|
74
83
|
|
|
75
84
|
## Best practices
|
|
76
85
|
|
|
77
|
-
- **Diagrams with connectors.** Lay the boxes out first, then `vector.connect` them (in one
|
|
78
|
-
`run_commands` batch) rather than drawing lines: connectors stay attached when boxes move, and
|
|
79
|
-
`route: "elbow"` with a small `radius` reads well in flowcharts. Leave sides `auto` unless a
|
|
80
|
-
connector must leave a particular side.
|
|
81
|
-
|
|
82
86
|
- **Frames for anything that resizes.** Build screens, cards, buttons and banners inside a frame
|
|
83
87
|
(`vector.addFrame` or `vector.frameSelection`), set each child's constraints with
|
|
84
88
|
`vector.setResizing` (backgrounds and dividers `leftRight`, icons pinned to their corner, titles
|
|
85
89
|
`center`), then resize with `vector.setFrame` to produce other sizes from one design. Export each
|
|
86
90
|
artboard with `vector.exportFrameSvg`.
|
|
87
|
-
|
|
88
|
-
- **Constraints for precise, editable geometry.** For technical drawings, diagrams, floor plans or
|
|
89
|
-
any shape that must keep exact relations, draw it roughly, run `vector.autoConstrain`, then add
|
|
90
|
-
the dimensions that matter (`vector.addConstraint` distance / angle / radius) and name them so
|
|
91
|
-
related sizes follow one another (`vector.setConstraintValue` with expressions like `w / 2`).
|
|
92
|
-
Check `vector.listConstraints` `dof`: aim for `fully` on parts that must not drift; `over`
|
|
93
|
-
means some constraints repeat others. Prefer few, meaningful constraints over many.
|
|
94
|
-
|
|
95
91
|
- **Symbols for repeats.** When the same art appears several times (icons in a set, trees in a
|
|
96
92
|
scene, bullets, a logo mark), make it once and place instances (`vector.makeSymbol`, then
|
|
97
93
|
`vector.placeSymbol` with `x` / `y` or a `transform`), so one edit in isolation changes them all.
|
|
98
94
|
Give each a clear name. Use `vector.setInstanceOptions` `fill` / `stroke` for colour variants and
|
|
99
95
|
`hidden` to drop a part, rather than detaching; detach only when one copy must diverge for good.
|
|
100
|
-
|
|
101
96
|
- **Text.** Use point text for labels and headlines, area text (`width`, `height`) for anything
|
|
102
97
|
that should wrap, text on a path for badges and seals. Check `overflow` in the summary every
|
|
103
98
|
text write returns: hidden lines or characters mean the box or path is too small. Keep one or
|
|
@@ -106,56 +101,11 @@ Parameter details (gradient fields, effect parameters, width profiles, envelopes
|
|
|
106
101
|
than splitting text into separate nodes, so it still edits as one paragraph. Create Outlines
|
|
107
102
|
only for final artwork or logos that must not depend on fonts; keep a copy as text if it may
|
|
108
103
|
change.
|
|
109
|
-
|
|
110
|
-
text
|
|
111
|
-
turns sideways; set a short Latin range `upright: true` to stack it letter by letter. In an
|
|
112
|
-
area, columns that do not fit past the left edge are `overflow`. Vertical text cannot follow a
|
|
113
|
-
path.
|
|
114
|
-
|
|
115
|
-
- **Clean paths.** Use the fewest anchors that describe the shape: anchors at extrema (top,
|
|
116
|
-
bottom, left, right of a curve) and at true corners, handles horizontal or vertical at extrema,
|
|
117
|
-
each handle about a third of its segment. Run `vector.removeRedundantPoints` after
|
|
118
|
-
booleans, knife and pencil work; `vector.smooth` for lumpy curves; `vector.deleteAnchors`
|
|
119
|
-
keeps the outline by refitting neighbours.
|
|
120
|
-
- **Live corners over drawn arcs.** Give rectangles, polygons and stars a `cornerRadius`
|
|
121
|
-
(`vector.addShape`) or set it with `vector.setCorners`; the anchors stay sharp and editable and
|
|
122
|
-
radii shrink to fit short sides. Keep one radius scale across a design. For app icons and UI
|
|
123
|
-
cards use `smoothing` (about 0.6) on `vector.setCorners` for continuous, squircle corners.
|
|
104
|
+
- **Paths, strokes, booleans, gradients, stacks, masks, shadows, connectors, constraints and
|
|
105
|
+
vertical text:** read [references/best-practices.md](references/best-practices.md) first.
|
|
124
106
|
- **Non-destructive first.** Live booleans (`vector.boolean` with `live: true`), path effects
|
|
125
107
|
and envelopes keep the operands editable. A destructive boolean or Pathfinder replaces its
|
|
126
108
|
inputs; do that last, and only when asked for flat output.
|
|
127
|
-
- **Boolean vs Pathfinder vs Shape Builder.** `vector.boolean` for a single result shape (union
|
|
128
|
-
for silhouettes, subtract for cut-outs, intersect, exclude). `vector.pathfinder` when you need
|
|
129
|
-
every region separately (divide), hidden parts removed (trim, merge) or a crop. Shape Builder
|
|
130
|
-
when you want to merge or delete particular regions picked by point.
|
|
131
|
-
- **Strokes.** Use `strokeAlign: "inside"` for icons and UI shapes so the outer size stays exact,
|
|
132
|
-
`outside` for outlines around artwork; open paths always stroke centred. Use round caps and
|
|
133
|
-
joins for friendly line icons, miter for geometric ones. Keep one stroke width across an icon
|
|
134
|
-
set (for example 1.5 or 2 units on a 24-unit grid).
|
|
135
|
-
- **Icon grid.** Use a square artboard (24, 32 or 48 units), keep a padding margin, place
|
|
136
|
-
coordinates on whole or half units so strokes render crisp, and align optical centres.
|
|
137
|
-
- **Gradients.** Two to four stops are usually enough. Use `interpolation: "oklab"` for
|
|
138
|
-
perceptually even blends without the grey or muddy midpoint sRGB gives between saturated hues;
|
|
139
|
-
`midpoint` to shift the half-way point instead of adding stops. Prefer linear or radial for
|
|
140
|
-
portability; conic and diamond export as SVG patterns.
|
|
141
|
-
- **Appearance stacks over duplicate shapes.** For a gradient over a texture, a tinted overlay,
|
|
142
|
-
an outline around an outline or a stroke inside and another outside, stack layers on one path
|
|
143
|
-
(`vector.addFill`, `vector.addStroke`) instead of copying the shape: the copies would drift
|
|
144
|
-
apart when edited. Give each stroke its own `strokeAlign` (outside under, inside or center on
|
|
145
|
-
top) and widths that step down towards the top. Use per-layer `opacity` and `blendMode`
|
|
146
|
-
(multiply to darken, screen to lighten, overlay for texture) rather than node opacity when only
|
|
147
|
-
one paint should fade.
|
|
148
|
-
- **Masks over cut-outs.** To show artwork inside a shape (a photo-like fill in a circle, a
|
|
149
|
-
pattern inside text-like outlines, a scene seen through a window), use
|
|
150
|
-
`vector.makeClippingMask` rather than a destructive intersect: the contents stay editable and
|
|
151
|
-
movable inside the frame. Draw the mask last (on top) and make the mask from it; edit the mask
|
|
152
|
-
later by its `maskId`. Use `vector.makeOpacityMask` with a white-to-black gradient for fades
|
|
153
|
-
and reflections (luminance: white shows). A clip's own paint is never drawn, so its fill
|
|
154
|
-
colour does not matter. Effects and shadows on the mask group follow the masked silhouette.
|
|
155
|
-
- **Shadows and glows.** Keep drop shadows subtle: black at 0.15 to 0.3 opacity, `offsetY` 2 to 8,
|
|
156
|
-
`blur` about twice the offset. Use one light direction for every shadow in a design. Filters
|
|
157
|
-
export as SVG filters, which some tools (and older renderers) draw slightly differently or not
|
|
158
|
-
at all; a design that must look identical everywhere should avoid them or rasterize.
|
|
159
109
|
- **Grouping and naming.** Group the parts of one object (`vector.group` with a `name`), name
|
|
160
110
|
paths that matter (`name` on add), and keep the tree shallow.
|
|
161
111
|
- **SVG hygiene.** Export with `precision` 2 or 3 (default 3); higher only for tiny artboards.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Drawing best practices
|
|
2
|
+
|
|
3
|
+
Detail for `SKILL.md`'s best practices, by topic.
|
|
4
|
+
|
|
5
|
+
- **Clean paths.** Use the fewest anchors that describe the shape: anchors at extrema (top,
|
|
6
|
+
bottom, left, right of a curve) and at true corners, handles horizontal or vertical at extrema,
|
|
7
|
+
each handle about a third of its segment. Run `vector.removeRedundantPoints` after
|
|
8
|
+
booleans, knife and pencil work; `vector.smooth` for lumpy curves; `vector.deleteAnchors`
|
|
9
|
+
keeps the outline by refitting neighbours.
|
|
10
|
+
- **Live corners over drawn arcs.** Give rectangles, polygons and stars a `cornerRadius`
|
|
11
|
+
(`vector.addShape`) or set it with `vector.setCorners`; the anchors stay sharp and editable and
|
|
12
|
+
radii shrink to fit short sides. Keep one radius scale across a design. For app icons and UI
|
|
13
|
+
cards use `smoothing` (about 0.6) on `vector.setCorners` for continuous, squircle corners.
|
|
14
|
+
- **Boolean vs Pathfinder vs Shape Builder.** `vector.boolean` for a single result shape (union
|
|
15
|
+
for silhouettes, subtract for cut-outs, intersect, exclude). `vector.pathfinder` when you need
|
|
16
|
+
every region separately (divide), hidden parts removed (trim, merge) or a crop. Shape Builder
|
|
17
|
+
when you want to merge or delete particular regions picked by point.
|
|
18
|
+
- **Strokes.** Use `strokeAlign: "inside"` for icons and UI shapes so the outer size stays exact,
|
|
19
|
+
`outside` for outlines around artwork; open paths always stroke centred. Use round caps and
|
|
20
|
+
joins for friendly line icons, miter for geometric ones. Keep one stroke width across an icon
|
|
21
|
+
set (for example 1.5 or 2 units on a 24-unit grid).
|
|
22
|
+
- **Icon grid.** Use a square artboard (24, 32 or 48 units), keep a padding margin, place
|
|
23
|
+
coordinates on whole or half units so strokes render crisp, and align optical centres.
|
|
24
|
+
- **Gradients.** Two to four stops are usually enough. Use `interpolation: "oklab"` for
|
|
25
|
+
perceptually even blends without the grey or muddy midpoint sRGB gives between saturated hues;
|
|
26
|
+
`midpoint` to shift the half-way point instead of adding stops. Prefer linear or radial for
|
|
27
|
+
portability; conic and diamond export as SVG patterns.
|
|
28
|
+
- **Appearance stacks over duplicate shapes.** For a gradient over a texture, a tinted overlay,
|
|
29
|
+
an outline around an outline or a stroke inside and another outside, stack layers on one path
|
|
30
|
+
(`vector.addFill`, `vector.addStroke`) instead of copying the shape: the copies would drift
|
|
31
|
+
apart when edited. Give each stroke its own `strokeAlign` (outside under, inside or center on
|
|
32
|
+
top) and widths that step down towards the top. Use per-layer `opacity` and `blendMode`
|
|
33
|
+
(multiply to darken, screen to lighten, overlay for texture) rather than node opacity when only
|
|
34
|
+
one paint should fade.
|
|
35
|
+
- **Masks over cut-outs.** To show artwork inside a shape (a photo-like fill in a circle, a
|
|
36
|
+
pattern inside text-like outlines, a scene seen through a window), use
|
|
37
|
+
`vector.makeClippingMask` rather than a destructive intersect: the contents stay editable and
|
|
38
|
+
movable inside the frame. Draw the mask last (on top) and make the mask from it; edit the mask
|
|
39
|
+
later by its `maskId`. Use `vector.makeOpacityMask` with a white-to-black gradient for fades
|
|
40
|
+
and reflections (luminance: white shows). A clip's own paint is never drawn, so its fill
|
|
41
|
+
colour does not matter. Effects and shadows on the mask group follow the masked silhouette.
|
|
42
|
+
- **Shadows and glows.** Keep drop shadows subtle: black at 0.15 to 0.3 opacity, `offsetY` 2 to 8,
|
|
43
|
+
`blur` about twice the offset. Use one light direction for every shadow in a design. Filters
|
|
44
|
+
export as SVG filters, which some tools (and older renderers) draw slightly differently or not
|
|
45
|
+
at all; a design that must look identical everywhere should avoid them or rasterize.
|
|
46
|
+
- **Diagrams with connectors.** Lay the boxes out first, then `vector.connect` them (in one
|
|
47
|
+
`run_commands` batch) rather than drawing lines: connectors stay attached when boxes move, and
|
|
48
|
+
`route: "elbow"` with a small `radius` reads well in flowcharts. Leave sides `auto` unless a
|
|
49
|
+
connector must leave a particular side.
|
|
50
|
+
|
|
51
|
+
- **Constraints for precise, editable geometry.** For technical drawings, diagrams, floor plans or
|
|
52
|
+
any shape that must keep exact relations, draw it roughly, run `vector.autoConstrain`, then add
|
|
53
|
+
the dimensions that matter (`vector.addConstraint` distance / angle / radius) and name them so
|
|
54
|
+
related sizes follow one another (`vector.setConstraintValue` with expressions like `w / 2`).
|
|
55
|
+
Check `vector.listConstraints` `dof`: aim for `fully` on parts that must not drift; `over`
|
|
56
|
+
means some constraints repeat others. Prefer few, meaningful constraints over many.
|
|
57
|
+
|
|
58
|
+
- **Vertical text.** For Japanese, Chinese or Korean set in columns (signage, book spines, posters), use vertical
|
|
59
|
+
text (`textOrientation: "vertical"` or `vector.setTextOrientation`): CJK stands upright, Latin
|
|
60
|
+
turns sideways; set a short Latin range `upright: true` to stack it letter by letter. In an
|
|
61
|
+
area, columns that do not fit past the left edge are `overflow`. Vertical text cannot follow a
|
|
62
|
+
path.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-vector-icon-set
|
|
3
|
+
description: The icon set workflow in the Pixerate vector editor - drawing line or solid icons on a consistent grid (24, 32 or 48 units) with padding and keylines, one stroke width, cap and join style and corner radius across the set, anchors on whole or half units so they render crisp, clean minimal paths, repeats as symbols, and checking and exporting each icon as SVG. Use when asked to draw, design, redraw, clean up or make consistent an icon or a set of icons, glyphs or pictograms, or to export icons as SVG, or when vector.addPath, vector.setStyle, vector.makeSymbol or vector.exportSvg are available. Load with pixerate-vector, which lists every command.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-vector
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Drawing an icon set
|
|
10
|
+
|
|
11
|
+
An icon set reads as one family when every icon shares the same few decisions: the grid, the
|
|
12
|
+
padding, the stroke width, the cap and join, the corner radius and the visual weight. Make
|
|
13
|
+
those decisions once, write them down, and apply them to every icon; then check each icon
|
|
14
|
+
against them in numbers (`vector.list` bounds, stroke widths and `d`) before looking.
|
|
15
|
+
|
|
16
|
+
This skill is the method. `pixerate-vector` has the full command reference; run the commands
|
|
17
|
+
with `run_command`, or several as one undo step with `run_commands` and `atomic: true`.
|
|
18
|
+
|
|
19
|
+
## Goal and quality bar
|
|
20
|
+
|
|
21
|
+
- One grid: a square artboard of 24 (UI), 32 or 48 units, the same for the whole set.
|
|
22
|
+
- Padding kept: nothing, stroke included, closer than 2 units to the edge on a 24 grid (1/12 of
|
|
23
|
+
the side on others). Strokes count half their width outside the path.
|
|
24
|
+
- One stroke width (2 on 24 is the common default; 1.5 for a lighter set), one cap and join
|
|
25
|
+
style (round for friendly sets, butt and miter for geometric ones), one corner radius.
|
|
26
|
+
- Crisp: with an even stroke width (2) put the path's anchors on whole units, with an odd one
|
|
27
|
+
(1, 3) on half units, so the stroke's edges fall on pixel boundaries at 1×.
|
|
28
|
+
- Clean paths: the fewest anchors that make the shape, no stray or zero-length segments, no
|
|
29
|
+
hidden overlapping shapes.
|
|
30
|
+
- Visually balanced: icons look the same size (circles a little larger than squares), and are
|
|
31
|
+
optically centred.
|
|
32
|
+
|
|
33
|
+
## Inputs to confirm
|
|
34
|
+
|
|
35
|
+
- **Grid and stroke:** the size (24 by default) and stroke width (2 by default), or an existing
|
|
36
|
+
icon to match (read it first).
|
|
37
|
+
- **Style:** outline (stroked) or solid (filled), and rounded or sharp.
|
|
38
|
+
- **Output:** one SVG per icon, a sprite, or symbols in one document.
|
|
39
|
+
|
|
40
|
+
## Working method
|
|
41
|
+
|
|
42
|
+
1. **Read what exists.** `vector.list`: the artboard size and any icons already drawn, with their
|
|
43
|
+
`strokeWidth`, `bounds` and `d`; `vector.getAppearance` for caps, joins and alignment.
|
|
44
|
+
**Check:** you can state the set's grid, padding, stroke, caps, joins and radius.
|
|
45
|
+
2. **Draw on the grid.** Prefer `vector.addShape` primitives (`rect` with `cornerRadius`,
|
|
46
|
+
`ellipse`, `line`, `polygon`) and `vector.addPath` with whole or half-unit coordinates; give
|
|
47
|
+
`fill: null`, `stroke`, `strokeWidth`, `strokeLinecap` and `strokeLinejoin` in the same call.
|
|
48
|
+
Batch one icon's parts in one `run_commands` with `atomic: true` and a `label`.
|
|
49
|
+
**Check:** `vector.list` bounds plus half the stroke stay inside the padding.
|
|
50
|
+
3. **Combine.** Join parts that should be one shape with `vector.boolean` (`union`; `subtract`
|
|
51
|
+
for cut-outs); use `vector.outlineStroke` only for a solid set or when the export target needs
|
|
52
|
+
fills. Then `vector.removeRedundantPoints`.
|
|
53
|
+
**Check:** `d` has no more anchors than the shape needs; coordinates are still on the grid.
|
|
54
|
+
4. **Match the set.** `vector.setStyle` on all the set's paths at once (`ids` from `vector.list`)
|
|
55
|
+
for stroke width, caps and joins; `vector.setCorners` for one radius.
|
|
56
|
+
**Check:** every path's `strokeWidth` is the set's; caps and joins in `vector.getAppearance`.
|
|
57
|
+
5. **Balance.** Compare the icons' bounds: a circle about 10 % larger than a square of the same
|
|
58
|
+
weight looks the same size; centre shapes optically (a play triangle shifts right).
|
|
59
|
+
**Check:** each icon's bounds centre is within half a unit of the artboard centre (or its
|
|
60
|
+
intended optical centre).
|
|
61
|
+
6. **Repeats become symbols.** The same element used several times (a star in a rating, a
|
|
62
|
+
chevron) is `vector.makeSymbol` once and `vector.placeSymbol` elsewhere.
|
|
63
|
+
7. **Export.** `vector.exportSvg` (or `vector.exportFrameSvg` per artboard) with `precision` 2;
|
|
64
|
+
read the markup: no transforms where plain coordinates would do, no hidden nodes.
|
|
65
|
+
|
|
66
|
+
## Decision table
|
|
67
|
+
|
|
68
|
+
| Need | Approach |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| Outline icon | Stroked paths, `fill: null`, round caps and joins |
|
|
71
|
+
| Solid icon | Filled shapes; `vector.outlineStroke` turns a stroked sketch into fills |
|
|
72
|
+
| A rounded rectangle or badge | `vector.addShape` `rect` with `cornerRadius`, not drawn arcs |
|
|
73
|
+
| A cut-out (a hole, a notch) | `vector.boolean` `subtract`, or `fillRule: "evenodd"` |
|
|
74
|
+
| Several icons on one page | One frame per icon (`vector.addFrame`), exported with `vector.exportFrameSvg` |
|
|
75
|
+
| One element repeated | `vector.makeSymbol` and `vector.placeSymbol` |
|
|
76
|
+
| Odd stroke width (1, 3) | Put anchors on half units so the edges land on pixels |
|
|
77
|
+
|
|
78
|
+
## Recipes
|
|
79
|
+
|
|
80
|
+
**A line icon.** One `vector.addPath` with `d` on the grid (`M12 5L12 19M5 12L19 12` is a plus
|
|
81
|
+
on 24), `fill: null`, `stroke`, `strokeWidth: 2`, `strokeLinecap: "round"`,
|
|
82
|
+
`strokeLinejoin: "round"`; then `vector.list` to check bounds against the padding.
|
|
83
|
+
|
|
84
|
+
**Make a set consistent.** `vector.list` → `vector.setStyle` with every path's id, the set's
|
|
85
|
+
`strokeWidth`, `strokeLinecap` and `strokeLinejoin` in one call (one undo step) → `vector.list`
|
|
86
|
+
to confirm nothing moved.
|
|
87
|
+
|
|
88
|
+
**Copies into a symbol.** `vector.makeSymbol` on the first copy (it is replaced by an
|
|
89
|
+
instance), `vector.delete` the other copies, `vector.placeSymbol` at their centres.
|
|
90
|
+
|
|
91
|
+
## Pitfalls
|
|
92
|
+
|
|
93
|
+
- **Bounds without the stroke.** `vector.list` bounds are the path's geometry; add half the
|
|
94
|
+
stroke width on each side when checking padding.
|
|
95
|
+
- **Off-grid anchors** after a scale or rotate blur at 1×; check `d` and round coordinates.
|
|
96
|
+
- **Default styles.** `vector.addShape` adds a grey fill and a dark 2-unit stroke unless you pass
|
|
97
|
+
`fill` and `stroke`; set them in the same call.
|
|
98
|
+
- **Scaling strokes.** Scaling an icon scales its geometry, not its stroke width; reset
|
|
99
|
+
`strokeWidth` afterwards so the set stays consistent.
|
|
100
|
+
- **Too many anchors** from booleans or traced input; `vector.removeRedundantPoints`.
|
|
101
|
+
- **Mixed caps and joins** across a set are easy to miss by eye; read `vector.getAppearance`.
|
|
102
|
+
|
|
103
|
+
## Tools used
|
|
104
|
+
|
|
105
|
+
- `vector.list`, `vector.getAppearance`: bounds, stroke widths, `d`, caps and joins
|
|
106
|
+
- `vector.addShape`, `vector.addPath`: draw on the grid
|
|
107
|
+
- `vector.setStyle`, `vector.setCorners`: one stroke, cap, join and radius across the set
|
|
108
|
+
- `vector.boolean`, `vector.outlineStroke`, `vector.removeRedundantPoints`: combine and clean paths
|
|
109
|
+
- `vector.transform`, `vector.align`: position and centre
|
|
110
|
+
- `vector.makeSymbol`, `vector.placeSymbol`, `vector.delete`: repeats as symbols
|
|
111
|
+
- `vector.addFrame`, `vector.exportFrameSvg`, `vector.exportSvg`: one artboard per icon, export
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "These three icons are from one set but their stroke widths don't match (1, 2 and 3 units). Make them all 2 units with round caps and joins, without moving or resizing them.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "vector",
|
|
5
|
+
"width": 72,
|
|
6
|
+
"height": 24,
|
|
7
|
+
"setup": [
|
|
8
|
+
{ "id": "vector.addPath", "params": { "d": "M4 12L20 12", "name": "Minus", "fill": null, "stroke": "#000", "strokeWidth": 1 } },
|
|
9
|
+
{ "id": "vector.addPath", "params": { "d": "M28 6L44 18M44 6L28 18", "name": "Close", "fill": null, "stroke": "#000", "strokeWidth": 2 } },
|
|
10
|
+
{ "id": "vector.addPath", "params": { "d": "M52 12L58 18L68 6", "name": "Check", "fill": null, "stroke": "#000", "strokeWidth": 3 } }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"checks": [
|
|
14
|
+
{ "why": "every icon at 2 units", "tool": "run_command", "args": { "id": "vector.list" }, "expect": { "result": { "nodes": { "$length": 3, "$every": { "strokeWidth": 2 } } } } },
|
|
15
|
+
{ "why": "the icons have not moved or changed size", "tool": "run_command", "args": { "id": "vector.list" }, "expect": { "result": { "nodes": [{ "name": "Minus", "bounds": { "x": 4, "y": 12, "width": 16, "height": 0 } }, { "name": "Close", "bounds": { "x": 28, "y": 6, "width": 16, "height": 12 } }, { "name": "Check", "bounds": { "x": 52, "y": 6, "width": 16, "height": 12 } }] } } },
|
|
16
|
+
{ "why": "round caps on the minus too", "tool": "run_command", "args": { "id": "vector.getAppearance", "params": { "id": { "$read": "run_command", "args": { "id": "vector.list" }, "path": "result.nodes.0.id" } } }, "expect": { "result": { "strokes": { "$every": { "strokeLinecap": "round", "strokeLinejoin": "round" } } } } }
|
|
17
|
+
],
|
|
18
|
+
"solution": [
|
|
19
|
+
{ "tool": "run_command", "args": { "id": "vector.list" } },
|
|
20
|
+
{ "tool": "run_command", "args": { "id": "vector.setStyle", "params": { "ids": { "$read": "run_command", "args": { "id": "vector.list" }, "path": "result.nodes.*.id" }, "strokeWidth": 2, "strokeLinecap": "round", "strokeLinejoin": "round" } } }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Draw a 'plus' line icon on this 24-unit icon grid: 2-unit strokes with round caps, no fill, centred, keeping a 2-unit padding around it.",
|
|
3
|
+
"fixture": { "editor": "vector", "width": 24, "height": 24 },
|
|
4
|
+
"checks": [
|
|
5
|
+
{ "why": "a stroked, unfilled path at the set's stroke width", "tool": "run_command", "args": { "id": "vector.list" }, "expect": { "result": { "nodes": { "$some": { "type": "path", "fill": null, "strokeWidth": 2 } } } } },
|
|
6
|
+
{ "why": "every stroke stays inside the 2-unit padding", "tool": "run_command", "args": { "id": "vector.list" }, "expect": { "result": { "nodes": { "$length": { "$min": 1 }, "$every": { "$expr": "!v.bounds || (v.bounds.x - (v.strokeWidth || 0) / 2 >= 1.99 && v.bounds.y - (v.strokeWidth || 0) / 2 >= 1.99 && v.bounds.x + v.bounds.width + (v.strokeWidth || 0) / 2 <= 22.01 && v.bounds.y + v.bounds.height + (v.strokeWidth || 0) / 2 <= 22.01)" } } } } },
|
|
7
|
+
{ "why": "centred on the grid", "tool": "run_command", "args": { "id": "vector.list" }, "expect": { "result": { "nodes": { "$some": { "type": "path", "bounds": { "$expr": "Math.abs(v.x + v.width / 2 - 12) <= 0.5 && Math.abs(v.y + v.height / 2 - 12) <= 0.5 && Math.abs(v.width - v.height) <= 0.5" } } } } } },
|
|
8
|
+
{ "why": "anchors on whole or half units, so strokes render crisp", "tool": "run_command", "args": { "id": "vector.list" }, "expect": { "result": { "nodes": { "$every": { "$expr": "!v.d || (v.d.match(/-?\\d*\\.?\\d+/g) || []).every((n) => Math.abs(n * 2 - Math.round(n * 2)) < 1e-6)" } } } } },
|
|
9
|
+
{ "why": "round caps", "tool": "run_command", "args": { "id": "vector.getAppearance", "params": { "id": { "$read": "run_command", "args": { "id": "vector.list" }, "path": "result.nodes.0.id" } } }, "expect": { "result": { "strokes": { "$some": { "strokeLinecap": "round" } } } } }
|
|
10
|
+
],
|
|
11
|
+
"solution": [
|
|
12
|
+
{ "tool": "run_command", "args": { "id": "vector.addPath", "params": { "d": "M12 5L12 19M5 12L19 12", "name": "Plus", "fill": null, "stroke": "#000000", "strokeWidth": 2, "strokeLinecap": "round", "strokeLinejoin": "round" } } },
|
|
13
|
+
{ "tool": "run_command", "args": { "id": "vector.list" } }
|
|
14
|
+
]
|
|
15
|
+
}
|