Matthias Dittgen

December 26, 2025

Halftone spiral

This post of mine is about a 3D-printed version of my avatar picture, drawn as a single, uninterrupted spiral. One line, without pauses or jumps.

I first came across this idea through a short video by @olegacy_3d on Instagram, showing a spiral portrait of Michael Jackson. I understood what was going on and decided to try it by myself. Not to copy the model, but to confirm that I actually understood the principle well enough to recreate it from scratch.

While writing this, it feels a bit like revealing a magic trick, which is strictly forbidden, isn’t it? Fortunately, this is not magic. It’s perception, geometry and a 3D printer following commands.

From parallel lines to grayscale»

The printer never stops. The nozzle follows a spiral path from the inside out, continuously. What changes along the way is not the direction, but the amount of material being extruded.

Where the filament line is wide, the surface appears dark. Where it is thin, it appears light. Seen from a distance, your brain blends these variations into a grayscale image. A black spiral on a white background suddenly becomes an image.

This is a halftone technique just with lines instead of dots.

Here you see parallel lines with constant spacing but varying width. As the scale gets smaller, the individual lines disappear and turn into a smooth grayscale gradient.

So the line width represents darkness, no matter the lines are straight, circular or spiral-shaped. And a spiral is a long, continuous path that can be printed.

Faster than expected»

According to my Mastodon microblog, I started on Tuesday, November 25th, because that’s when the Chaos Meetup takes place here in Offenburg at the local makerspace and Chaos Computer Club spin-off Section77. I enjoy chatting with like minded people about 3D printing, and since they some weren’t there yet and other were deeply focused, I didn’t want to get bored, so I decided to explore this spiral idea.

Although the end goal was a 3D-printed object, the first step was purely digital. I built a small proof of concept in the browser using JavaScript, HTML, and SVG. Later, I wrapped it in Svelte so I could make it interactive and share it here.

The first version came together that same evening—helped, in part, by AI. It worked, but it was quite messy code. So I stepped back, cleaned it up, and reduced the problem to a few very clear steps.

How to approach»

At its core, the whole process looks like this:

Along the way, I introduced a number of parameters, so the result can be tweaked and tuned later.

No code, just concepts»

I’m not sharing the code in detail here. In the age of AI-assisted development (eg. in Visueal Studio code), that feels less meaningful than it used to. I admit AI was involved through editor suggestions and the occasional “how would you approach this?” question in a chat window for inspiration. But I avoid vibe coding and understand all the parts and put them together myself.

But when it comes to GCode generation, this would normally mean studying the Marlin GCode reference and burning a few failed prints along the way. This turned out to be a much lower bar to pass using AI.

Line width with SVG and GCode»

But let us highlight a few parts anyway. In SVG you cannot vary the stroke-width along a single path. To work around this, the line must be represented as a filled polygon. For every sample point along the spiral, two coordinates are generated, one offset outward, one inward. Together they form a very huge polygon with the shape of our spiral line with varying line-width.

With GCode, it’s different. Line width is controlled by how much filament is extruded while moving. More material means a wider and less material means a narrower line of Filament being extruded.

A command like this already contains everything we need:

G1 X80.00 Y80.00 E0.0040 F1500

Broken down:

By adjusting only the E value from move to move, the printer naturally produces thicker or thinner lines. There’s no outline and infill, just a single line.

Try it yourself»

Below is the final tool. You can experiment with my avatar image or upload your own and explore the parameters. I recommaned a square image with good contrast, maybe one that already looks great when in greyscale.

Warning:
The generated GCode is a demonstration only. It extrudes only the black spiral and does not include a base layer, priming, or printer-specific safety settings. I am not responsible for any damage. Use at your own risk.




turns: 33 turns: 33
smooth: 0.02 smooth: 0.02
gamma: 1.8 gamma: 1.8
min-width: 0.2 min-width: 0.2





A note for Bambulab users»

First, I created a very flat cylinder and exported it as GCode using BambuStudio. Then I inserted the GCode from above before the line ; MACHINE_END_GCODE_START and adjusted the Z in the line G1 Z0.2 F600 ; move to start Z height so that it is above the upper layer of the cylinder by the layer height.

While you can use BambuStudio to render the Gcode from above, it failed for me to render it together with the cylinder. I just ignored that.

I usually upload the GCode to the printer via FTP anyway, to save myself the trouble of using the SD card. I use lftp on my Mac.

# install lftp
brew install lftp

# connect and accept certificate whatsoever
lftp -u bblp,[ACCESS_CODE] ftps://[PRINTER_IP]:990 -e "set ssl:verify-certificate no;"

# when connected, copy you gcode to the printer
PUT [FILENAME].gcode

I can then select the file on the printer display to print it. The printer then pauses while I switch from white to black filament.