comicforge 0.0.1__py3-none-any.whl
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.
- comicforge/__init__.py +10 -0
- comicforge/__main__.py +5 -0
- comicforge/_skill/SKILL.md +325 -0
- comicforge/_skill/reference.md +644 -0
- comicforge/bubbles.py +133 -0
- comicforge/cli.py +396 -0
- comicforge/inspire.py +246 -0
- comicforge/library.py +235 -0
- comicforge/pixelart.py +80 -0
- comicforge/render.py +560 -0
- comicforge/scaffold.py +144 -0
- comicforge/scene.py +112 -0
- comicforge/validate.py +192 -0
- comicforge-0.0.1.dist-info/METADATA +136 -0
- comicforge-0.0.1.dist-info/RECORD +17 -0
- comicforge-0.0.1.dist-info/WHEEL +4 -0
- comicforge-0.0.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
# ComicForge Authoring Guide
|
|
2
|
+
|
|
3
|
+
This guide covers everything you need to author comics with ComicForge — for both
|
|
4
|
+
humans and LLMs. The [SKILL.md](SKILL.md) file is the quick-reference contract;
|
|
5
|
+
this document goes deeper on structure, paths, and how all the pieces fit together.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Engine + self-contained projects
|
|
10
|
+
|
|
11
|
+
ComicForge is a content-free engine plus self-contained projects. There is **no
|
|
12
|
+
shared asset library** — every project owns all of its own art:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
comicforge/ The engine — pure code, ships no art of any kind.
|
|
16
|
+
examples/<name>/ A self-contained comic project: its own characters,
|
|
17
|
+
scenes, pixel art, and page specs. Nothing is shared.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
A real downstream project just installs the engine and creates the same four
|
|
21
|
+
asset folders; none of the example art ships with the engine. The `examples/`
|
|
22
|
+
directory is where we develop against the engine the same way a downstream project
|
|
23
|
+
would.
|
|
24
|
+
|
|
25
|
+
### Starting a new project — `cmf init`
|
|
26
|
+
|
|
27
|
+
A project is **data only** — YAML specs + SVG / pixel art — so it needs no Python.
|
|
28
|
+
Install the engine globally and scaffold a project:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
uv tool install comicforge # puts `cmf` on your PATH; no venv per project
|
|
32
|
+
cmf init my-comic # scaffold the four asset dirs + a starter page
|
|
33
|
+
cd my-comic && cmf render pages/hello.yaml
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`cmf init <dir>` creates `characters/`, `scenes/`, `pixel/`, `pages/`, a renderable
|
|
37
|
+
`pages/hello.yaml`, a seed `pixel/heart.yaml`, a `.gitignore`, a project `README`,
|
|
38
|
+
and a copy of this skill under `.claude/skills/comicforge/`. It is idempotent —
|
|
39
|
+
existing files are left alone unless you pass `--force`. After upgrading the engine,
|
|
40
|
+
re-run `cmf init --force <dir>` to refresh the bundled skill.
|
|
41
|
+
|
|
42
|
+
Only reach for a `pyproject.toml` (with `comicforge` as a dependency, run via
|
|
43
|
+
`uv run cmf`) when you need a pinned engine version or your own render scripts.
|
|
44
|
+
|
|
45
|
+
### `comicforge/` — the engine
|
|
46
|
+
|
|
47
|
+
The `comicforge/` package is the rendering engine. It loads assets, composes SVG,
|
|
48
|
+
and writes output. It contains **no concrete content** — no hardcoded characters,
|
|
49
|
+
no default scenes, no built-in sprites. All asset directories must be provided
|
|
50
|
+
explicitly (via the spec or CLI flags).
|
|
51
|
+
|
|
52
|
+
Modules:
|
|
53
|
+
- `library.py` — loads characters, stacks overlays, places them in panels
|
|
54
|
+
- `scene.py` — loads scene backgrounds, scales them to cover a panel/canvas
|
|
55
|
+
- `pixelart.py` — resolves pixel-art sprites from a directory or inline spec
|
|
56
|
+
- `render.py` — composes full pages, individual panels, and standalone scenes
|
|
57
|
+
- `bubbles.py` — speech, thought, and shout bubbles with word-wrap and tails
|
|
58
|
+
- `cli.py` — the `comicforge` command and its subcommands
|
|
59
|
+
|
|
60
|
+
### A project — `examples/<name>/`
|
|
61
|
+
|
|
62
|
+
Each project is one directory owning everything it needs. Page specs live under
|
|
63
|
+
`pages/` and point at the sibling asset folders one level up:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
examples/pes/
|
|
67
|
+
characters/ Character art: an identity that owns one or more poses
|
|
68
|
+
tom/ flat (single pose): everything in one viewBox
|
|
69
|
+
base.svg
|
|
70
|
+
face-neutral.svg
|
|
71
|
+
face-happy.svg
|
|
72
|
+
arms-down.svg
|
|
73
|
+
...
|
|
74
|
+
character.yaml
|
|
75
|
+
bara/ posed: shared faces at root + a body per pose
|
|
76
|
+
character.yaml
|
|
77
|
+
face-neutral.svg shared, re-anchored onto each pose
|
|
78
|
+
face-happy.svg
|
|
79
|
+
poses/
|
|
80
|
+
sit/ { pose.yaml, base.svg }
|
|
81
|
+
walk/ { pose.yaml, base.svg }
|
|
82
|
+
scenes/ Scene backgrounds: base SVG + overlay SVGs + scene.yaml
|
|
83
|
+
dvur/
|
|
84
|
+
base.svg
|
|
85
|
+
weather-clear.svg
|
|
86
|
+
weather-rain.svg
|
|
87
|
+
scene.yaml
|
|
88
|
+
pokoj/
|
|
89
|
+
base.svg
|
|
90
|
+
scene.yaml
|
|
91
|
+
pixel/ Pixel-art sprites: one YAML file per sprite
|
|
92
|
+
heart.yaml
|
|
93
|
+
sun.yaml
|
|
94
|
+
star.yaml
|
|
95
|
+
bone.yaml
|
|
96
|
+
pages/ The comic specs
|
|
97
|
+
slepice.yaml 2×2 comic page — Tom and Bára watch chickens
|
|
98
|
+
kosticka.yaml Strip with scenes, pixel art, and multiple bubble types
|
|
99
|
+
dvur-scene.yaml Standalone illustration — single scene filling the canvas
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Each spec references its project's assets via relative paths (`../characters`,
|
|
103
|
+
`../scenes`, `../pixel`).
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Asset loading and path resolution
|
|
108
|
+
|
|
109
|
+
The engine needs three asset directories. Each can be provided in the spec file or
|
|
110
|
+
via a CLI flag:
|
|
111
|
+
|
|
112
|
+
| Purpose | Spec key | CLI flag |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| Character library | `library:` | `--library` |
|
|
115
|
+
| Scene backgrounds | `scenes_dir:` | `--scenes` |
|
|
116
|
+
| Pixel-art sprites | `pixel_dir:` | `--pixel-dir` |
|
|
117
|
+
|
|
118
|
+
### Path resolution rule
|
|
119
|
+
|
|
120
|
+
**Relative paths in the spec are resolved against the spec file's directory.**
|
|
121
|
+
|
|
122
|
+
For example, `examples/pes/pages/slepice.yaml` contains:
|
|
123
|
+
```yaml
|
|
124
|
+
library: "../characters"
|
|
125
|
+
pixel_dir: "../pixel"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
When you run from the repo root:
|
|
129
|
+
```bash
|
|
130
|
+
cmf render examples/pes/pages/slepice.yaml -o out.png
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The engine resolves `../characters` relative to `examples/pes/pages/` — arriving
|
|
134
|
+
at `examples/pes/characters`. This means you can always run from the repo root
|
|
135
|
+
regardless of where specs live.
|
|
136
|
+
|
|
137
|
+
**CLI flags are used as-is** (relative to your current working directory, or
|
|
138
|
+
absolute). A CLI flag overrides the spec key entirely.
|
|
139
|
+
|
|
140
|
+
### When a directory is missing
|
|
141
|
+
|
|
142
|
+
If `library:` is absent from the spec and `--library` was not passed, the engine
|
|
143
|
+
raises:
|
|
144
|
+
```
|
|
145
|
+
ValueError: library directory is required but was not provided.
|
|
146
|
+
Set 'library:' in the spec or pass the corresponding CLI flag.
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
If `scenes_dir:` is absent but the spec uses a `scene:` key in a panel, the engine
|
|
150
|
+
raises at render time when the scene is first accessed.
|
|
151
|
+
|
|
152
|
+
`pixel_dir:` is optional — you only need it when using `{art: <name>}` references.
|
|
153
|
+
Inline `{grid: [...], palette: {...}}` specs work with no pixel library at all.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Authoring a character
|
|
158
|
+
|
|
159
|
+
A character is an **identity** (name, label, …) that owns one or more **poses**.
|
|
160
|
+
A pose is a body drawn in its own viewBox; expressions (`face`, …) can be *shared*
|
|
161
|
+
across poses, authored once. There are two on-disk shapes.
|
|
162
|
+
|
|
163
|
+
### Single-pose (flat)
|
|
164
|
+
|
|
165
|
+
The classic layout — everything in one viewBox:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
<project>/characters/<name>/
|
|
169
|
+
base.svg The body drawn in a fixed local viewBox
|
|
170
|
+
<slot>-<variant>.svg Overlays in the same viewBox (one per variant)
|
|
171
|
+
character.yaml Manifest: name, label, viewbox, slots, default
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
# character.yaml
|
|
176
|
+
name: tom # internal ID (must match directory name)
|
|
177
|
+
label: Tom # display name for the UI / manifest
|
|
178
|
+
viewbox: [200, 320] # local canvas size [width, height]
|
|
179
|
+
default:
|
|
180
|
+
face: neutral # default variant for each slot (used when omitted in spec)
|
|
181
|
+
arms: down
|
|
182
|
+
slots:
|
|
183
|
+
arms: [down, wave, point, crossed, hips, thumbsup]
|
|
184
|
+
face: [neutral, happy, surprised, sad, angry, laugh, wink]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
All SVG files (base + overlays) share **the same viewBox**. The engine strips each
|
|
188
|
+
to its inner markup and stacks base → overlays in the slot order from
|
|
189
|
+
`character.yaml`.
|
|
190
|
+
|
|
191
|
+
### Multiple poses
|
|
192
|
+
|
|
193
|
+
When the **body** changes between poses (legs differ sit vs walk), each pose gets
|
|
194
|
+
its own `base.svg`. Shared overlays (faces) live at the character root and are
|
|
195
|
+
re-registered onto each pose via an **anchor** — a reference point (typically the
|
|
196
|
+
head centre) they are drawn around.
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
<project>/characters/<name>/
|
|
200
|
+
character.yaml Identity + SHARED slots + default + pose list
|
|
201
|
+
<slot>-<variant>.svg SHARED overlays, drawn around the canonical anchor
|
|
202
|
+
poses/
|
|
203
|
+
<pose>/
|
|
204
|
+
pose.yaml viewbox, anchor, optional pose-specific slots/default
|
|
205
|
+
base.svg this pose's body
|
|
206
|
+
<slot>-<variant>.svg optional pose-specific overlays
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```yaml
|
|
210
|
+
# character.yaml
|
|
211
|
+
name: bara
|
|
212
|
+
label: Bára
|
|
213
|
+
anchor: [120, 72] # canonical point the shared overlays are drawn around
|
|
214
|
+
slots: {face: [neutral, happy]} # SHARED across poses, re-anchored per pose
|
|
215
|
+
default: {pose: sit, face: neutral}
|
|
216
|
+
poses: [sit, walk]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```yaml
|
|
220
|
+
# poses/walk/pose.yaml
|
|
221
|
+
viewbox: [240, 180]
|
|
222
|
+
anchor: [132, 64] # where THIS pose's head-centre lands
|
|
223
|
+
slots: {arms: [trot]} # OPTIONAL — pose-specific slots + overlays
|
|
224
|
+
default: {arms: trot}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
For each shared overlay the engine emits a `translate(pose.anchor − character.anchor)`
|
|
228
|
+
so one `face-happy.svg` re-registers onto every pose. **Translate-only**: keep the
|
|
229
|
+
head the same *size* across poses; only its position changes. A flat character is
|
|
230
|
+
just the posed model with one implicit pose at anchor `[0, 0]`.
|
|
231
|
+
|
|
232
|
+
### Composition and placement
|
|
233
|
+
|
|
234
|
+
`Character.compose_inner(selection, pose)` assembles, in draw order:
|
|
235
|
+
1. The chosen pose's `base.svg`
|
|
236
|
+
2. That pose's pose-specific overlays (in `pose.yaml` slot order)
|
|
237
|
+
3. The shared overlays (in `character.yaml` slot order), each wrapped in the
|
|
238
|
+
anchor-translate
|
|
239
|
+
|
|
240
|
+
`Character.place(selection, cx, cy, height, flip, pose)` wraps the result in a `<g
|
|
241
|
+
transform>` that scales it to `height` px tall (using the pose's viewBox) and
|
|
242
|
+
centres it at `(cx, cy)`. Omit `pose` to use `default.pose`.
|
|
243
|
+
|
|
244
|
+
### Adding a pose or expression
|
|
245
|
+
|
|
246
|
+
- **New expression (shared):** draw it around the canonical anchor, save as
|
|
247
|
+
`<name>/<slot>-<variant>.svg`, add the variant to `slots` in `character.yaml`.
|
|
248
|
+
- **New pose:** add `poses/<pose>/` with its own `base.svg` + `pose.yaml`
|
|
249
|
+
(set its `anchor`), and list `<pose>` under `poses:`.
|
|
250
|
+
- **Flat character, new overlay:** save `<name>/<slot>-<variant>.svg` in the shared
|
|
251
|
+
viewBox and add it to `slots`.
|
|
252
|
+
|
|
253
|
+
No code changes needed.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Authoring a scene
|
|
258
|
+
|
|
259
|
+
A scene is a full-panel illustrated background. It follows the same model as a
|
|
260
|
+
character: `base.svg` + optional overlay files + `scene.yaml`.
|
|
261
|
+
|
|
262
|
+
### Directory layout
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
<project>/scenes/<name>/
|
|
266
|
+
base.svg The background drawn in a fixed local viewBox
|
|
267
|
+
<slot>-<variant>.svg Optional: weather effects, props, etc.
|
|
268
|
+
scene.yaml Manifest
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### scene.yaml
|
|
272
|
+
|
|
273
|
+
```yaml
|
|
274
|
+
name: dvur
|
|
275
|
+
label: Dvůr
|
|
276
|
+
viewbox: [320, 200]
|
|
277
|
+
default:
|
|
278
|
+
weather: clear
|
|
279
|
+
slots:
|
|
280
|
+
weather: [clear, rain]
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
A scene with no slots has only `name`, `label`, and `viewbox`.
|
|
284
|
+
|
|
285
|
+
### Cover-scaling behavior
|
|
286
|
+
|
|
287
|
+
Scenes are rendered with `cover` scaling: the scene is scaled so its **smaller
|
|
288
|
+
dimension fits** and the other dimension may overflow, always filling the full panel
|
|
289
|
+
with no empty borders. The scene is centred within the panel.
|
|
290
|
+
|
|
291
|
+
### Using a scene in a spec
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
# In a panel:
|
|
295
|
+
scene: dvur # default slots
|
|
296
|
+
scene: {name: dvur, weather: rain} # pick a slot variant
|
|
297
|
+
|
|
298
|
+
# In a standalone scene spec:
|
|
299
|
+
type: scene
|
|
300
|
+
scene:
|
|
301
|
+
name: dvur
|
|
302
|
+
weather: clear
|
|
303
|
+
scale: 3 # px per scene unit (canvas = viewbox × scale)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Confirm available scenes and slots with:
|
|
307
|
+
```bash
|
|
308
|
+
cmf scenes --scenes examples/pes/scenes
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Authoring pixel art
|
|
314
|
+
|
|
315
|
+
Pixel art is defined as a grid of characters mapped to colors. It can come from
|
|
316
|
+
the library or be defined inline in the spec.
|
|
317
|
+
|
|
318
|
+
### Project sprites (`<project>/pixel/<name>.yaml`)
|
|
319
|
+
|
|
320
|
+
Each file contains `grid` (list of equal-length strings) and `palette` (map of
|
|
321
|
+
character to hex color):
|
|
322
|
+
|
|
323
|
+
```yaml
|
|
324
|
+
# examples/pes/pixel/heart.yaml
|
|
325
|
+
palette:
|
|
326
|
+
R: "#e8556d"
|
|
327
|
+
r: "#b83e54"
|
|
328
|
+
grid:
|
|
329
|
+
- ".RR..RR."
|
|
330
|
+
- "RRRRRRRR"
|
|
331
|
+
- "RRRRRRRR"
|
|
332
|
+
- "RRRRRRRR"
|
|
333
|
+
- ".RRRRRR."
|
|
334
|
+
- "..RRRR.."
|
|
335
|
+
- "...RR..."
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The transparent-cell convention: `.` (period) or ` ` (space) = transparent.
|
|
339
|
+
Any other character maps to a color in `palette`.
|
|
340
|
+
|
|
341
|
+
### Inline sprites
|
|
342
|
+
|
|
343
|
+
Define the grid and palette directly in the panel spec:
|
|
344
|
+
|
|
345
|
+
```yaml
|
|
346
|
+
pixel:
|
|
347
|
+
- grid: ["....", ".RR.", ".RR.", "...."]
|
|
348
|
+
palette: {R: "#e8556d"}
|
|
349
|
+
x: 0.5
|
|
350
|
+
y: 0.5
|
|
351
|
+
scale: 0.3
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Inline sprites require no library. The `pixel_dir:` spec key is only needed when
|
|
355
|
+
using `{art: <name>}` references.
|
|
356
|
+
|
|
357
|
+
### Using pixel art in a spec
|
|
358
|
+
|
|
359
|
+
```yaml
|
|
360
|
+
# From the project's pixel/ dir (needs pixel_dir: in the spec):
|
|
361
|
+
pixel_dir: "../pixel"
|
|
362
|
+
# ...
|
|
363
|
+
pixel:
|
|
364
|
+
- art: heart
|
|
365
|
+
x: 0.8
|
|
366
|
+
y: 0.25
|
|
367
|
+
scale: 0.18
|
|
368
|
+
|
|
369
|
+
# Inline (no pixel_dir needed):
|
|
370
|
+
pixel:
|
|
371
|
+
- grid: ["XX", "XX"]
|
|
372
|
+
palette: {X: "#ff0000"}
|
|
373
|
+
x: 0.5
|
|
374
|
+
y: 0.5
|
|
375
|
+
scale: 0.2
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Coordinates: `x`/`y` are panel fractions (0–1); `scale` = sprite height as a
|
|
379
|
+
fraction of panel height. Multiple pixel items can appear in one panel.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## CLI reference
|
|
384
|
+
|
|
385
|
+
Run all commands from the repo root with `cmf <cmd>`.
|
|
386
|
+
|
|
387
|
+
**Default output.** `render`, `scene`, `panel`, and `character` accept `-o`, but
|
|
388
|
+
when you omit it they write a timestamped file into the gitignored `output/` dir
|
|
389
|
+
(e.g. `output/slepice-20260614-191613.png`). Successive renders accumulate so you
|
|
390
|
+
can compare how a page or character evolved; pass `-o` to pin an exact path.
|
|
391
|
+
|
|
392
|
+
### `init` — scaffold a new project
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
cmf init <dir> [--force]
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Creates a data-only project at `<dir>`: the `characters/`, `scenes/`, `pixel/`,
|
|
399
|
+
`pages/` asset dirs, a renderable `pages/hello.yaml`, a seed `pixel/heart.yaml`, a
|
|
400
|
+
`.gitignore`, a `README`, and a copy of this skill under `.claude/skills/comicforge/`.
|
|
401
|
+
Idempotent — existing files are kept unless `--force` is given.
|
|
402
|
+
|
|
403
|
+
### `render` — render a comic page
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
cmf render <spec.yaml> [-o <output>] # default: output/<spec>-<timestamp>.png
|
|
407
|
+
[--library <dir>] # override spec's library: key
|
|
408
|
+
[--scenes <dir>] # override spec's scenes_dir: key
|
|
409
|
+
[--pixel-dir <dir>] # override spec's pixel_dir: key
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Output format is determined by the file extension: `.svg`, `.png`, or `.pdf`.
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
cmf render examples/pes/pages/slepice.yaml -o slepice.png
|
|
416
|
+
cmf render examples/pes/pages/slepice.yaml -o slepice.pdf
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### `validate` — check a spec without rendering
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
cmf validate <spec.yaml>
|
|
423
|
+
[--library <dir>] # override spec's library: key
|
|
424
|
+
[--scenes <dir>] # override spec's scenes_dir: key
|
|
425
|
+
[--pixel-dir <dir>] # override spec's pixel_dir: key
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Statically checks a page or `scene` spec against the libraries it points at and
|
|
429
|
+
reports **every** problem at once — no output is drawn. It catches things
|
|
430
|
+
`render` doesn't: `render` stops at the first unrenderable item and *silently
|
|
431
|
+
ignores* keys it doesn't recognise, so a typo like `fcae: happy` or `post: walk`
|
|
432
|
+
renders a default-faced, default-posed actor with no error. `validate` flags:
|
|
433
|
+
|
|
434
|
+
- characters / scenes / pixel sprites missing from the library
|
|
435
|
+
- poses an actor asks for that the character lacks
|
|
436
|
+
- slot variants that don't exist for the chosen character+pose / scene
|
|
437
|
+
- actor / scene keys that aren't reserved and aren't a real slot (likely typos)
|
|
438
|
+
- bubble `speaker` naming no actor in the panel; unknown bubble `kind`
|
|
439
|
+
- structural holes (a page with no `rows`, a row with no `panels`, a bubble with
|
|
440
|
+
no `text`); an unknown `type:`, or a `type:` that contradicts the structure
|
|
441
|
+
(a `scene` spec carrying `rows`)
|
|
442
|
+
|
|
443
|
+
Exits `0` when the spec is sound, `1` (with a bulleted problem list) otherwise —
|
|
444
|
+
so it works in a pre-render check or CI.
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
cmf validate examples/pes/pages/slepice.yaml # -> "...: ok"
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### `scene` — render a standalone illustration
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
cmf scene <spec.yaml> [-o <output>] # default: output/<spec>-<timestamp>.png
|
|
454
|
+
[--library <dir>]
|
|
455
|
+
[--scenes <dir>]
|
|
456
|
+
[--pixel-dir <dir>]
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
cmf scene examples/pes/pages/dvur-scene.yaml -o dvur.png
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
A scene spec should declare `type: scene` (page specs default to `type: page`).
|
|
464
|
+
`render` and `scene` each reject the wrong spec type with a clear message
|
|
465
|
+
pointing at the other command, rather than crashing.
|
|
466
|
+
|
|
467
|
+
### `panel` — render individual panels for review
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
cmf panel <spec.yaml> [-o <output>] # default: output/<spec>-r<R>c<C>-<ts>.png
|
|
471
|
+
[--row 0] [--col 0] # which panel (0-indexed)
|
|
472
|
+
[--all] # render every panel into a directory
|
|
473
|
+
# (default: output/<spec>-panels-<ts>/)
|
|
474
|
+
[--scale 0.5] # size vs full-page (default 0.5 = low res)
|
|
475
|
+
[--library <dir>]
|
|
476
|
+
[--scenes <dir>]
|
|
477
|
+
[--pixel-dir <dir>]
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
cmf panel examples/pes/pages/slepice.yaml -o panel.png --row 0 --col 1
|
|
482
|
+
cmf panel examples/pes/pages/kosticka.yaml -o panels/ --all
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### `character` — render one character on its own
|
|
486
|
+
|
|
487
|
+
Quick visual test of a single character — no page, no panel, just the composed
|
|
488
|
+
character cropped to its pose on a plain canvas.
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
cmf character <name> [selection ...] --library <dir>
|
|
492
|
+
[-o <output>] # default: output/<name>-<timestamp>.png
|
|
493
|
+
[--pose <pose>] # which pose (default: the character's default)
|
|
494
|
+
[--scale 2.0] # px per viewBox unit
|
|
495
|
+
[--bg "#ffffff"] # canvas background colour (white by default)
|
|
496
|
+
[--flip] # mirror horizontally
|
|
497
|
+
[--thumb-px 320] # body width of the small companion PNG (0 to skip)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Each `selection` token is either a bare name — matched first against the
|
|
501
|
+
character's **poses**, then against the variants of any **slot** — or an explicit
|
|
502
|
+
`key=value` (`pose=walk`, `face=happy`). Unknown tokens error with the available
|
|
503
|
+
poses and slots listed.
|
|
504
|
+
|
|
505
|
+
Writes **two files**: the full render at `-o`, and a smaller `<stem>.small.png`
|
|
506
|
+
beside it (body ≈ `--thumb-px` wide) — read the small one to spend fewer tokens.
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
cmf character bara sit happy --library examples/pes/characters -o bara.png
|
|
510
|
+
cmf character bara pose=walk face=neutral --library examples/pes/characters
|
|
511
|
+
cmf character tom --library examples/pes/characters # default pose -> tom.png
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### `characters` — list a project's characters
|
|
515
|
+
|
|
516
|
+
```bash
|
|
517
|
+
cmf characters --library <dir>
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
The `--library` flag is **required** (no default). Prints a JSON manifest of every
|
|
521
|
+
character, its slots, available variants, and defaults.
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
cmf characters --library examples/pes/characters
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### `scenes` — list a project's scenes
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
cmf scenes --scenes <dir>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
The `--scenes` flag is **required** (no default). Prints a JSON manifest of every
|
|
534
|
+
scene and its slots.
|
|
535
|
+
|
|
536
|
+
```bash
|
|
537
|
+
cmf scenes --scenes examples/pes/scenes
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### `inspire` — generate reference images from a theme + descriptions
|
|
541
|
+
|
|
542
|
+
Paints **inspiration** art (a reference to author SVG from), *not* shipped assets.
|
|
543
|
+
Reads a project theme (`theme.yaml`) and a list of descriptions (`references.yaml`),
|
|
544
|
+
composes one prompt per item, and calls the Replicate API.
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
cmf inspire <references.yaml> -o <out_dir>
|
|
548
|
+
[--theme <theme.yaml>] # default: theme.yaml beside the spec
|
|
549
|
+
[--only id1,id2] # generate only these ids
|
|
550
|
+
[--force] # regenerate even if a .png already exists
|
|
551
|
+
[--dry-run] # compose prompts only — no API call, no token
|
|
552
|
+
[--review] # also write a review.html grid of images + prompts
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Each item writes `<out_dir>/<id>.png` + `<id>.prompt.txt`. `theme.yaml` and the
|
|
556
|
+
output dir default to siblings of the references spec.
|
|
557
|
+
|
|
558
|
+
**Theme** (`<project>/theme.yaml`) — applied to every image:
|
|
559
|
+
|
|
560
|
+
| key | meaning |
|
|
561
|
+
|---|---|
|
|
562
|
+
| `style` | the look, in words (prepended to every prompt) |
|
|
563
|
+
| `palette` | list of hex colors fed to the model as a color scale |
|
|
564
|
+
| `mood` | one-line mood/tone |
|
|
565
|
+
| `negative` | what to avoid; default discourages text/marks |
|
|
566
|
+
| `aspect_ratio` | e.g. `"1:1"` (default) |
|
|
567
|
+
| `model` | Replicate model id (default `google/imagen-3`) |
|
|
568
|
+
|
|
569
|
+
**References** (`<project>/references.yaml`) — a `items:` list (or a bare list).
|
|
570
|
+
Each entry needs an id (`id`/`name`) and a description (`prompt`/`description`/`desc`).
|
|
571
|
+
|
|
572
|
+
Live generation requires the optional extra and a token:
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
pip install "comicforge[inspire]" # adds replicate + python-dotenv
|
|
576
|
+
export REPLICATE_API_TOKEN=... # or put it in a .env beside the spec
|
|
577
|
+
cmf inspire examples/pes/references.yaml --review
|
|
578
|
+
cmf inspire examples/pes/references.yaml --dry-run # no token needed
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
The composed prompt is `style` → `Subject: <prompt>` → palette → mood → negative,
|
|
582
|
+
joined by blank lines (preview it with `--dry-run` and the `.prompt.txt` sidecar).
|
|
583
|
+
Do **not** auto-vectorize the result — hand/LLM-author the SVG using it as a guide.
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## Spec reference
|
|
588
|
+
|
|
589
|
+
For the full spec grammar, see [SKILL.md](SKILL.md). Key points:
|
|
590
|
+
|
|
591
|
+
- **Page-level keys**: `title`, `page` (A4/A5/letter/[w,h]), `px_per_mm`,
|
|
592
|
+
`margin_mm`, `gutter_mm`, `library`, `scenes_dir`, `pixel_dir`
|
|
593
|
+
- **Rows and panels**: `rows[].height` (relative weight), `rows[].panels[].width`
|
|
594
|
+
(relative weight), panel keys: `bg`, `scene`, `actors`, `pixel`, `bubbles`
|
|
595
|
+
- **Actor keys**: `char`, `pose` (optional; defaults to the character's default
|
|
596
|
+
pose), per-slot variant keys (`face`, `arms`, etc.), `x`, `y`, `scale`, `flip`
|
|
597
|
+
- **Bubble keys**: `text`, `kind` (speech/thought/shout), `speaker`, optional
|
|
598
|
+
`x`/`y`/`to`/`max_chars`/`fs`
|
|
599
|
+
- **Pixel keys**: `art` (library name) or `grid`+`palette`, plus `x`/`y`/`scale`
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
## Python API
|
|
604
|
+
|
|
605
|
+
```python
|
|
606
|
+
from comicforge import render_spec, render_scene, load_spec, PixelLibrary
|
|
607
|
+
|
|
608
|
+
# Render a full comic page
|
|
609
|
+
render_spec("examples/pes/pages/slepice.yaml", "slepice.png")
|
|
610
|
+
|
|
611
|
+
# Render a standalone scene
|
|
612
|
+
render_scene("examples/pes/pages/dvur-scene.yaml", "dvur.png")
|
|
613
|
+
|
|
614
|
+
# Load a spec dict for inspection
|
|
615
|
+
spec = load_spec("examples/pes/pages/slepice.yaml")
|
|
616
|
+
|
|
617
|
+
# Pass asset dirs explicitly (overrides spec keys)
|
|
618
|
+
from comicforge.library import Library
|
|
619
|
+
from comicforge.scene import SceneLibrary
|
|
620
|
+
lib = Library("examples/pes/characters")
|
|
621
|
+
scn = SceneLibrary("examples/pes/scenes")
|
|
622
|
+
px = PixelLibrary("examples/pes/pixel")
|
|
623
|
+
render_spec("examples/pes/pages/kosticka.yaml", "out.png",
|
|
624
|
+
library=lib, scenes=scn, pixel_library=px)
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## Demo pages at a glance
|
|
630
|
+
|
|
631
|
+
All three pages live in the `examples/pes/` project.
|
|
632
|
+
|
|
633
|
+
| Page | Spec | Description |
|
|
634
|
+
|---|---|---|
|
|
635
|
+
| `slepice` | `examples/pes/pages/slepice.yaml` | 2×2 page — Tom tells Bára to watch the chickens. No scenes. Pixel: sun, bone, heart. |
|
|
636
|
+
| `kosticka` | `examples/pes/pages/kosticka.yaml` | Comic strip — scenes (`pokoj`, `dvur`), pixel art, speech/thought/shout bubbles. |
|
|
637
|
+
| `dvur-scene` | `examples/pes/pages/dvur-scene.yaml` | Standalone illustration — farmyard, Tom and Bára. Render with `comicforge scene`. |
|
|
638
|
+
|
|
639
|
+
Render all demos from the repo root:
|
|
640
|
+
```bash
|
|
641
|
+
cmf render examples/pes/pages/slepice.yaml -o slepice.png
|
|
642
|
+
cmf render examples/pes/pages/kosticka.yaml -o kosticka.png
|
|
643
|
+
cmf scene examples/pes/pages/dvur-scene.yaml -o dvur.png
|
|
644
|
+
```
|