Drawing Machine

How it works

Layers that can each be swapped: where a target comes from, what draws it, what edits it, what reads it, and what records it.

sources Midjourney / FLUX / ChatGPTmuseum.py (open access)scene.py (still-life stage)authored value shapesa drawing (yours, ours) targets only: no generatedpixel is pasted onto a canvas compose.pyvalue plan: place, mass,lighten, squint, quiet,from_canvas Hands (run.mjs)a learned hand's marks,each stroke must pay--init · --absolute the canvasscalar .f32 + strokeslift.py: kneaded eraserintend.mjs: 5 kernels the criticread.py: canon, tonal,marks, colour, route+ passes (RIDP ... Marrowline) propose.pyreading -> intention job(overlay to read first) the recordstep.py: the ledgerlineage_video.pyexact replay of every edit

The motor: Hands

Hands (Russell's engine at parallaxmetrology/hands) is vendored as a read-only snapshot in vendor/hands/. A hand is a learned vocabulary of marks: width, length, pressure, softness, how a mark follows the form, whether it hatches. hand/run.mjs looks at a target, then lays strokes greedily on a real canvas: a stroke is kept only when it lowers the drawing's error by more than it costs. Restraint makes open paper worth something. --init continues an existing canvas; --absolute keeps a target's own darks (see Tests).

The first test: a Midjourney image as target, 4335 Kollwitz strokes in 5.7 s.
The first test: a Midjourney image as target, 4335 Kollwitz strokes in 5.7 s.
Sheet 4, stage A: a composed value plan drawn by Hands (4285 strokes).
Sheet 4, stage A: a composed value plan drawn by Hands (4285 strokes).

Value plans: compose.py

A target is assembled, never pasted: place (an oval crop, optionally blurred and cut to a few values: a block-in), mass and blob (authored value shapes), lighten, squint, quiet (a busy field made into a slow even tone) and from_canvas (outside a box, the target is the current drawing, so continued drawing works only inside it). The base can be an image or blank paper.

Plan A: p1 #4 structure, a panel removed, a default face placed.
Plan A: p1 #4 structure, a panel removed, a default face placed.
Plan C3: a second head placed as a blurred 3-value block-in.
Plan C3: a second head placed as a blurred 3-value block-in.
Sheet 5: a plan with no image at all, only authored shapes.
Sheet 5: a plan with no image at all, only authored shapes.

The still-life stage: scene.py

A small signed-distance raymarcher: a table, an optional wall, apples (squashed spheres with a dimple and a stem, which can lean), cups (tapered and hollow with a floor and a handle, or capped), boxes; one directional light with soft shadows, ambient occlusion and an optional pool of light. It gives authored geometry with correct cast shadows, a source of form that is not a generator. hand/thumbs.py draws a whole set from scene files and makes caption-free contact sheets.

The first stage render: a cup and two apples, one light from the front left.
The first stage render: a cup and two apples, one light from the front left.
Hands draws it at thumbnail size; it leaves the mid-grey table as paper.
Hands draws it at thumbnail size; it leaves the mid-grey table as paper.
A value study from the stage.
A value study from the stage.
Hands drawing it.
Hands drawing it.
The same scene, the cup capped.
The same scene, the cup capped.
Without the hollow the cup dissolves into paper.
Without the hollow the cup dissolves into paper.

Taking back: lift.py

A kneaded eraser in broad soft passes across a box; the paper's tooth keeps dust in its valleys, so a lifted passage leaves a ghost. The passes are recorded like marks.

Before: the default face drawn.
Before: the default face drawn.
After: taken back to a ghost.
After: taken back to a ghost.

Intentions: intend.mjs

A critic's note is translated into a kernel: a loss measured on the canvas inside a region. The machine proposes native actions (Hands vocabulary marks, eraser strokes, a stump), renders each on a copy, rejects any that break a guard, and commits the one that lowers the loss most for the least change; then it looks again. Nothing here decides what the drawing needs; the job does.

kernellossactionsguard
reserve-paperink above a ceiling in a regioneraser strokesregion, protected boxes, change cap
connect-massink below a floor along a corridor (5 probes across)broad mark, hatch patch, or a loose cluster of mid-width strokessoftness: no new step across the corridor (a mass, not a stripe)
soften-edgecontrast across an edge above a ceilingpaired feather: lift on the dark side, deposit on the light sideedge integrity: no new light pocket, the boundary moves at most n px
break-edgea ruled boundary: too little wander from its own straight linepush (a cluster of the hand's strokes outside the edge) or pull (eraser inside)no station moves more than maxShift
even-tonecells of a masked field off a target tone, or too busy insidebroad flat marks into light cells, eraser through dark cells, a stump through busy cellsmask: no change outside the field

Intentions run in order on one canvas; after each, every earlier one is re-measured and the new one is reverted if it undid an earlier commitment. The committed commands are replayed from the source and must match exactly, or nothing is written.

v1: the black and the neck came out as stripes.
v1: the black and the neck came out as stripes.
With the softness guard: a soft black mass.
With the softness guard: a soft black mass.
break-edge with a broad mark: ribbed bands.
break-edge with a broad mark: ribbed bands.
With clusters of the hand’s own strokes: the edge is drawn.
With clusters of the hand’s own strokes: the edge is drawn.
A ChatGPT image loaded as a canvas: a busy ground.
A ChatGPT image loaded as a canvas: a busy ground.
even-tone with the stump: the field reads as blur. Not kept.
even-tone with the stump: the field reads as blur. Not kept.
even-tone as a veil: the pattern loses contrast and keeps its grain.
even-tone as a veil: the pattern loses contrast and keeps its grain.

The critic

From the the paint simulator build: critic/read.py runs the VTL Full Readout notebook (frozen verbatim, hash recorded), a tonal reader (value zones, anchors, focus, visibility, edges), a mark reader (energy, direction, stratification), Russell's colour analyser, and Protocol v8 routing. Every number carries what it computes, what it may say and what it cannot say; a guard checks written critiques against the packet. The engine passes (RIDP, Sketcher, Artist's Lens, Marrowline, Handoff) read it in order. In this build the passes were written by the orchestrating assistant, which is recorded in each critique.

The default map, numbered for measurement: twelve Midjourney images of the still-life prompt.
The default map, numbered for measurement: twelve Midjourney images of the still-life prompt.
A reader sheet: the picture, 3 value zones, mark zones, mark energy, stroke direction.
A reader sheet: the picture, 3 value zones, mark zones, mark energy, stroke direction.

Every reader sheet in this build

sheet4 · C2
sheet4 · C2
sheet4 · C3
sheet4 · C3
sheet4/gpt · H1
sheet4/gpt · H1
sheet4/gpt · gpt1
sheet4/gpt · gpt1
sheet5/artist · K
sheet5/artist · K
sheet5 · A
sheet5 · A
sheet5/gpt · H1
sheet5/gpt · H1
sheet5/gpt · gpt1
sheet5/gpt · gpt1
sheet5/loop2 · O
sheet5/loop2 · O
sheet5/loop2 · P
sheet5/loop2 · P
sheet5/loop2 · gpt2a
sheet5/loop2 · gpt2a
sheet5/loop2 · gpt2b
sheet5/loop2 · gpt2b
sheet5/loop3 · gpt3
sheet5/loop3 · gpt3
still-life/loop1/artist · G
still-life/loop1/artist · G
still-life/loop1/artist · T7
still-life/loop1/artist · T7
still-life/loop1/artist · final
still-life/loop1/artist · final
still-life/loop1 · gpt1
still-life/loop1 · gpt1

The translator: propose.py

A deterministic step from the critic's reading to an intention job: no committed black, a ruled edge (side scans of the smoothed mass), a mass floating near the main one. Each proposal cites the number that fired it, and the job is drawn over the canvas so it can be read before it runs.

A job the translator wrote for sheet 4: two ruled edges (blue), a black, two joins (red).
A job the translator wrote for sheet 4: two ruled edges (blue), a black, two joins (red).

Sources: museum.py

Searches open-access collections for public-domain works (the Art Institute of Chicago answers; the Met's API returned HTTP 410 from this machine) and downloads one object at a time on request, with a sidecar record. A reference is a target or a study, like any other source. The searches run so far (catalogue records only; nothing has been downloaded yet):

Museum search results
keyartisttitledatesearch
aic:87Salvator Rosa Italian, 1615-1673A warrior sits on a block and gestures; two figure are behind him, from Figurine seriesn.d.figure from behind drawing
aic:164Attributed to a Member of the Earlier Mannerist Group Greek; AthensColumn-Krater (Mixing Bowl)about 460 BCEfigure from behind drawing
aic:162Attributed to a painter of the Leagros group Greek; AthensHydria (Water Jar)about 515-500 BCEfigure from behind drawing
aic:246Attributed to the Mattinata Painter Greek; Apulia, ItalyOinochoe (Pitcher)end of 4th century BCEfigure from behind drawing
aic:615Attributed to the Euaion Painter Greek; AthensPyxis (Container for Personal Objects)450-440 BCEfigure from behind drawing
aic:616Greek; AthensPyxis (Container for Personal Objects)430-420 BCEfigure from behind drawing
aic:65244RomanStatue of a Seated Woman2nd centuryKollwitz seated woman
aic:40599Antonis Mor (Netherlandish, c. 1517–c. 1576)Portrait of a Seated Womanc. 1565Kollwitz seated woman
aic:15860Paul Gauguin French, 1848-1903Seated Breton Woman1886Kollwitz seated woman
aic:150773Georges Seurat French, 1859-1891Seated Woman with a Parasol (study for La Grande Jatte)1884/85Kollwitz seated woman
aic:246Attributed to the Mattinata Painter Greek; Apulia, ItalyOinochoe (Pitcher)end of 4th century BCEKollwitz seated woman
aic:35381ChinaSeated Woman Holding MirrorTang dynasty (A.D. 618–907), first half of 8th centuryKollwitz seated woman
aic:4Lawrence Carmichael EarlePriest and Boyn.d.Kollwitz
aic:93Jean François MilletGeese in a Farmyardc. 1871Kollwitz
aic:149032Alessandro MagnascoStudy Sheet with Seated Figures1705/15seated figure
aic:46468Jean Baptiste Carpeaux (French, 1827-1875)Back View of Seated Figure, Lifting Left Armn.d.seated figure
aic:210894Herman SaftlevenA Forest Interior, with a Seated Figuren.d.seated figure
aic:81690Follower of Michelangelo BuonarrotiMale Figure Study, with Slight Sketch of Seated Figure1540/50seated figure
aic:7120Giovanni Francesco GrimaldiFigures Seated by a Rivern.d.seated figure
aic:96181Sir Edward Burne-JonesSeated Female Figure1873seated figure
aic:112804Niccolò Circignani, called Il PomarancioStudy for the Duel between Heraclius and Khosrau (recto); Sketches of Seated Figure (verso)1582seated figure
aic:95697Attributed to Vincenzo TamagniSketches of Virgin and Child, Seated Figure, and Landscapec. 1530seated figure

The record: step.py and lineage_video.py

The ledger writes one line per move: tool, platform, inputs and outputs with checksums, the prompt verbatim, and who made the call; --show lays the chain out as a page. The lineage video shows every full-image state in order, with a tree: the spine is the line kept, side branches were tried and not carried forward, each with its reason.

The end card of one lineage section: the kept line as tiles, the branches tried at each step beside it.
The end card of one lineage section: the kept line as tiles, the branches tried at each step beside it.

Parked: the first PyTorch build

Before Hands, the machine was built from scratch in PyTorch: a dry-media material (tooth, graphite grades, compressed charcoal reaching L* 9), a motor with characters, and a FRIDA-style planner calibrated against the real motor. The residual halved over nine rounds, but it did not read as hands. It is parked, not deleted.

Material specimen: grades, charcoal, characters, stump, eraser.
Material specimen: grades, charcoal, characters, stump, eraser.
Planner v2 against its target.
Planner v2 against its target.
Planner v2, pass by pass.
Planner v2, pass by pass.