Right, this is the one I've been building up to for the whole physical chapter. Last episode I flashed a Raspberry Pi, made a sketch survive a plug-pull, and promised that next time we'd stop learning the pieces one at a time and actually build something whole. Well, here we are. Today you and I make a real, physical piece of generative art. Not a simulation. Not a thing on a screen that pretends to be an object. Something you can hold in your hands, hang on a wall, or hand to a friend and watch them turn it over.
I want to be honest about what this episode is. It's a mini-project, like the poster back in episode 8 or the artificial ecosystem in episode 60, which means there's less new technique and a lot more putting it all together. Everything we need we already learned - the plotter from episode 105, the laser cutter from 108, the LEDs from 109, the 3D print from 112, the Pi from last time. What's new is the pipeline that turns code into a thing that exists in the world. Allez, let's make some art you can actually touch :-).
Before a single line of code, you pick your output medium, because the medium decides the constraints and the constraints decide the art. This is the opposite of screen work, where you can draw literally anything. A physical piece can only be what the machine and the material allow, and honestly? That's a gift. Constraints are where the good stuff lives.
Here's the menu, the same four workhorses we met across this chapter, with the tradeoffs I actually think about when I choose:
// femdev's physical-output menu (notes-as-code, not a program :-)
const mediums = {
plotter: "pen on paper. cheap, gorgeous, PROVEN. lines only. best first piece.",
laser: "cut/engrave wood, acrylic, card. flat shapes -> 3D when assembled.",
led: "addressable light. luminous, animated, spatial. needs wiring + power.",
print3d: "sculptural, mathematical, novel. slow, fiddly, but truly 3D objects.",
};
// my advice for piece #1: PLOTTER. lowest cost of failure, highest beauty-per-effort.
For this walkthrough I'm going to build with the pen plotter, because it's the one where you'll actually finish and feel proud instead of drowning in G-code and failed prints. But every stage below maps straight onto the other three - I'll point out where they differ as we go. Pick the medium that excites you most, then follow the same pipeline.
A plotter draws lines. That's it. It can't fill a solid area (well, it can, but it takes an hour of scribbling and murders your pen). It has a physical drawing area - mine is A4, roughly 190 by 270 mm of usable space once you allow a margin for the clips. The pen has a real width, maybe 0.3mm, so two lines closer than that just smear into one. These aren't annoyances to fight, they're the rules of the game, and a good generative system is designed knowing them from line one.
So the very first thing I do is set up my canvas in real millimetres, not pixels. This is the mental shift that makes physical output click - you're not drawing to a screen, you're describing a machine's movements in the real world.
// think in MILLIMETRES from the start. this is a real page, not a screen.
const page = {
width: 190, // usable drawing width in mm (A4 minus margins)
height: 270, // usable drawing height in mm
margin: 12, // keep the pen away from the clips at the edges
};
// map a 0..1 generative coordinate onto the real page, respecting the margin.
function toPage(nx, ny) {
return {
x: page.margin + nx * (page.width - 2 * page.margin),
y: page.margin + ny * (page.height - 2 * page.margin),
};
}
Everything my algorithm produces lives in that clean 0-to-1 space, and toPage is the single place that knows about real-world dimensions. If I switch to a bigger plotter next month, I change three numbers and nothing else. That separation - abstract design in normalised space, one translation layer to the physical - is a habbit worth burning into your brain for any physical work.
Now the fun part. The algorithm. You can use anything from this whole series here - noise fields, L-systems from episode 54, Voronoi, particle traces, reaction-diffusion from episode 52. The only real requirement is that it produces varied-but-consistently-good output across different seeds, because in a minute we're going to generate a whole edition and want every one of them to be a keeper.
I'm reaching for a flow field, because plotters and flow fields are a match made in heaven - long, flowing, continuous pen strokes that the machine draws in one smooth motion. We built the guts of this back in the Perlin noise episode (12) and the particle episodes. A particle drops onto the page, reads the noise angle under it, steps that way, reads again, and traces a path as it drifts. Hundreds of those paths together make that soft, windswept look.
// one flowline: a particle drifts through a noise field, recording its path.
// this is episode 12's perlin noise steering episode 11's particles - on paper now.
function traceFlowline(startX, startY, noise, steps = 200, stepLen = 0.004) {
const path = [{ x: startX, y: startY }];
let x = startX, y = startY;
for (let i = 0; i < steps; i++) {
// noise gives a value 0..1; stretch it to a full-circle angle
const angle = noise(x * 3, y * 3) * Math.PI * 2;
x += Math.cos(angle) * stepLen;
y += Math.sin(angle) * stepLen;
if (x < 0 || x > 1 || y < 0 || y > 1) break; // walked off the page
path.push({ x, y });
}
return path; // a list of 0..1 points = one continuous pen stroke
}
Then I seed a few hundred of these across the page. Notice I'm keeping strokes that are too short out of the final set - a plotter dropping the pen for a 2mm dash looks like a mistake, not a mark. Design decisions like that, made in code, are what separate a clean plot from a scratchy mess.
// scatter many flowlines across the page to build the whole composition.
function composeArtwork(noise, count = 400) {
const strokes = [];
for (let i = 0; i < count; i++) {
// pseudo-random start, but derived so the SAME seed gives the SAME art
const sx = fract(Math.sin(i * 12.9898) * 43758.5453);
const sy = fract(Math.sin(i * 78.233) * 43758.5453);
const path = traceFlowline(sx, sy, noise);
if (path.length > 15) strokes.push(path); // drop the stubby ones
}
return strokes;
}
const fract = (v) => v - Math.floor(v);
Here's the thing that turns "a nice picture" into "an edition you can sell": the whole piece must be reproducible from a single seed. We covered exactly this in episode 24, and it matters ten times more once output is physical. If a plot gets damaged, or a collector wants number 7 of the edition, you need to regenerate the identical artwork on demand. A seeded random generator makes the randomness deterministic - same seed in, same art out, forever.
// a tiny seeded PRNG (mulberry32). feed it a seed, get a repeatable stream.
// episode 24's whole point: 'random' art you can regenerate on command.
function makeRandom(seed) {
return function () {
seed |= 0; seed = (seed + 0x6D2B79F5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
// build a seeded noise + composition. seed 42 ALWAYS makes the same plot.
function generatePiece(seed) {
const rand = makeRandom(seed);
const noise = makeSeededNoise(rand); // your episode-12 noise, seeded by rand
return { seed, strokes: composeArtwork(noise) };
}
Write that seed number on the back of every physical piece. Genuinly. It's the artwork's fingerprint, and years from now it's how you prove which plot is which.
This is how generative art actually enters the physical art world, and it's such a lovely idea. You don't design one image. You design a system, then let it spit out a whole family of variations, one per seed. You look at all of them with a curator's eye, and you pick the best handful. Those chosen few become your edition - each one unique, but unmistakably siblings from the same generative parent.
// generate a whole gallery of candidates, one per seed, ready to judge by eye.
function generateEdition(seeds) {
return seeds.map((seed) => {
const piece = generatePiece(seed);
return {
seed,
strokeCount: piece.strokes.length,
// rough 'ink budget' - total pen travel. handy for spotting the busy ones.
inkLength: piece.strokes.reduce((sum, s) => sum + s.length, 0),
strokes: piece.strokes,
};
});
}
const candidates = generateEdition([1, 7, 12, 23, 42, 88, 101, 256, 777, 999]);
// now render all ten small, side by side, and pick the 3-5 that sing.
I render all ten as little thumbnails on screen first, because previewing on the display costs nothing and plotting costs paper, ink and twenty minutes each. Some seeds give a tangled hairball, some give a boring wisp, and two or three give that oh feeling. Those are your edition. Ruthless selection is a skill - the system makes plenty, your taste makes it art.
Now we cross the bridge from code to machine. A plotter doesn't understand my array of points - it wants SVG (which plotter software converts to G-code) or G-code directly. SVG is the friendly choice, and it's the same format we exported for the blockchain back in episode 29, so you already know the shape of it. Each flowline becomes one <polyline>, coordinates in real millimetres.
// turn our 0..1 strokes into a real-millimetre SVG the plotter can draw.
function toSVG(strokes) {
const w = page.width, h = page.height;
let out = `<svg xmlns="http://www.w3.org/2000/svg" `
+ `width="${w}mm" height="${h}mm" viewBox="0 0 ${w} ${h}">\n`;
for (const stroke of strokes) {
const pts = stroke.map((p) => {
const { x, y } = toPage(p.x, p.y); // 0..1 -> real mm
return `${x.toFixed(2)},${y.toFixed(2)}`;
}).join(" ");
// no fill! a plotter only draws the OUTLINE. stroke-width ~ the pen.
out += ` <polyline points="${pts}" fill="none" `
+ `stroke="black" stroke-width="0.3"/>\n`;
}
return out + "";
}
Two things in there are pure physical-world thinking. fill="none" because a pen has nothing to fill with - it only traces edges, a lesson every plotter beginner learns the hard way. And stroke-width="0.3" to match the actual pen, so the on-screen preview honestly shows how dense the real plot will be. If your screen preview looks solid black, your real page will be a soggy over-inked mess. Preview at pen width and you plot what you see.
If you chose the laser cutter instead, this is where you'd output cut-lines and engrave-lines on separate SVG layers. For 3D printing, you'd generate an STL mesh (episode 112). For LEDs, there's no file at all - your "output" is the running sketch driving the strip live. Same pipeline, different final artefact.
Code done. Now the part that humbles everyone: you press go on the physical machine and reality has opinions. I cannot stress enough - the first physical output always reveals problems the screen hid. Lines you thought were separate merge. The pen skips on the first pass because it's not primed. A stroke runs 3mm off the page because you forgot the margin somewhere. This is normal. This is the work.
// a pre-flight checklist for the MACHINE, before you waste good paper.
const plotChecklist = {
testCorners: "plot just the 4 corner points first - confirms alignment + size.",
penPrime: "scribble on scrap so ink is flowing before the real plot.",
paperClip: "tape/clip the page FLAT. a lifted corner = a smeared diagonal.",
slowFirst: "run the first real plot at reduced speed. catch problems early.",
watchIt: "do NOT walk away on plot #1. be ready to hit stop.",
};
Run a corner test, prime the pen on scrap, clip the paper down properly, and watch the first one draw. When it finishes, you'll see things the screen never told you - and you go back, tweak the code (fewer strokes, thicker margin, a different seed), and plot again. My first flow-field plot had 400 strokes and looked like a dust storm; I dropped it to 180 and suddenly it could breathe. That iteration loop, screen to paper and back, is the actual craft of physical generative art.
Here's where a lot of coders stop, and it's a shame, because finishing is what turns "a printout" into "a piece". A raw plot fresh off the machine is a prototype. Give it ten more minutes of care and it becomes something someone would hang on a wall.
For a plotter print: let the ink dry properly (smudging a finished plot is a special kind of heartbreak), trim it clean, and mount it on a mat board with a real frame and glass. The mat border does an astonishing amount of work - suddenly it looks intentional, gallery-ish, valuable. For a 3D print you'd sand the layer lines and maybe prime and paint. For a laser piece you'd sand the scorched edges and oil the wood. For LEDs you'd diffuse the harsh points behind frosted acrylic and tidy the solder joints. Different medium, same principle: the last 10% of finishing delivers 50% of the perceived quality.
While the piece is fresh, document it - properly, not a phone snap in bad light. This is the difference between a hobby and a practice, and it costs you fifteen minutes. Photograph the finished piece in good, soft daylight. Photograph the making process (the plotter mid-draw is a beautiful shot). Screenshot the code. And write a short honest record of what it actually is.
// the metadata that turns a nice object into a documented ARTWORK.
const artworkRecord = {
title: "Driftfield No. 3",
algorithm: "seeded perlin flow field, 180 strokes",
seed: 42, // the fingerprint. regenerate anytime.
medium: "0.3mm archival pen on 200gsm cotton paper",
dimensions: "190 x 270 mm image, framed 320 x 400 mm",
editionOf: 5, // this is 3 of 5
made: "2026-07",
};
That little record IS your portfolio entry. It's what goes in the frame's back, in your online shop listing, in the email to a gallery. Notice the seed is right there - with the code and that number, this exact piece can be reborn from nothing. That's a genuinly magical property physical art has never had before generative code gave it to us.
Quick honest word on cost and value, because artists love to skip it and then wonder why they're broke. Materials have a real price - maybe 10 euros for good plotter paper and archival ink, 30 for a laser-cutting service run, 50 for 3D print filament and a failed print or two. Your time has value on top of that. If you sell, price accordingly: small generative prints in editions typically go for anywhere from 50 to a few hundred euros depending on size, edition size, and how known you are. Don't give the work away because "it's just code" - the code is exactly the hard part, and the physical object is genuinely scarce.
Once your piece is framed and hanging, do the exercise that actually deepens you as an artist: put it next to the screen version and really compare. What's better on paper? What's lost?
// the tradeoff, honestly. neither wins - they're different joys.
const physicalVsScreen = {
physicalGains: "PRESENCE. exists in space, catches real light, ages, is scarce.",
physicalLoses: "MOTION. it's frozen. no animation, no interaction, no response.",
screenGains: "DYNAMISM. it moves, reacts to you, updates, lives forever online.",
screenLoses: "OBJECTHOOD. it's pixels. infinitely copyable, never quite 'real'.",
};
The paper piece has presence - it's in the room with you, the light rakes across the ink differently at dawn and dusk, it'll yellow gently over decades like anything real. But it's frozen; all that beautiful motion from our animation episodes is gone. The screen version moves and responds but has no weight, no scarcity, no thereness. Neither is better. Understanding the trade in your bones makes you sharper in either medium, and it plants a question I want you chewing on: what if a piece could be both at once? Hold that thought - it's exactly where we're headed next.
This is the most ambitious exercise in the entire series, and I'm not going to shrink it, because the payoff is real. Execute the complete pipeline, every stage, all the way to a thing you can hold:
// THE CAPSTONE. do every step. the output is a REAL object, not a screenshot.
const capstone = {
1: "CHOOSE medium (plotter is my rec for piece #1). know its constraints.",
2: "DESIGN the generative system in normalised 0..1 space (reuse ANY episode).",
3: "SEED it - make the whole piece reproducible from one number (episode 24).",
4: "GENERATE an edition of 10-20 variations. render them small on screen.",
5: "SELECT the best 3-5 by eye. ruthless taste. these are your edition.",
6: "PRODUCE - export SVG/STL, run the machine, WATCH the first one.",
7: "ITERATE - fix what the physical output reveals. plot again.",
8: "FINISH - dry, trim, frame/sand/diffuse. the last 10% is 50% of quality.",
9: "DOCUMENT - photograph it well, record algorithm + seed + medium.",
};
Take your time with this. It's not a one-afternoon sketch, it's a proper little project, and that's the point - at the end you have a real, tangible artwork that exists in the physical world because you wrote code. Hang it up. Show a friend. Watch their face when you explain a computer drew it from a single number. That feeling is why we do any of this.
fill="none" and a real pen-width stroke, because a pen only traces edges and your preview must tell the truthSo there it is - you've gone the whole distance, from a blank canvas in episode 1 to a framed object hanging on your wall, drawn by a machine from a number you chose. That's a genuinly complete loop, and if you actually do the capstone you'll feel it click in a way no amount of reading ever gives you.
But did you catch the question I slipped in near the end? Physical has presence, screen has motion - and I asked what if a piece could be both at once. That's not a rhetorical flourish, that's a door. A plot that's also alive. An object that also responds. The wall between "a thing you hold" and "a thing that moves" is thinner than it looks, and pulling it down is exactly what we start doing next time. Go make your piece first, though - hang it somewhere you'll see it every day - and come back ready to make it breathe :-).
Sallukes! Thanks for reading.
X