<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matthew Armstrong</title>
    <description>The latest articles on DEV Community by Matthew Armstrong (@open-madscience).</description>
    <link>https://dev.to/open-madscience</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4171433%2F8deeb383-d1eb-4ec0-955e-612d43f55925.png</url>
      <title>DEV Community: Matthew Armstrong</title>
      <link>https://dev.to/open-madscience</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/open-madscience"/>
    <language>en</language>
    <item>
      <title>Skipper Flipper</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Fri, 30 Oct 2026 08:19:54 +0000</pubDate>
      <link>https://dev.to/open-madscience/skipper-flipper-12o2</link>
      <guid>https://dev.to/open-madscience/skipper-flipper-12o2</guid>
      <description>&lt;p&gt;&lt;em&gt;An experimental ESP32 pseudo Klipper port.&lt;/em&gt; (Repository and package name: &lt;code&gt;esp-printhost&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPL-3.0&lt;/strong&gt; · Linux (Mint / Ubuntu) · C++17 core + Python tools · &lt;a href="https://The-Dorkknight.github.io/esp-printhost/" rel="noopener noreferrer"&gt;Project page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Licensed under the GNU General Public License v3.0, see &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/LICENSE" rel="noopener noreferrer"&gt;LICENSE&lt;/a&gt;. &lt;strong&gt;No warranty and no liability:&lt;/strong&gt; this is provided as is. You use it at your own risk, on your own hardware, and the author is not responsible for damage to boards, printers or anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is
&lt;/h2&gt;

&lt;p&gt;An idea for reviving old, donated 3D printers: a cheap ESP32-S3 becomes the "brain" (WiFi, web UI, text config, motion planning) and the printer's original board becomes a simple slave running a modified Klipper MCU firmware. The full plan is in &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/DESIGN.md" rel="noopener noreferrer"&gt;docs/DESIGN.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fak9gyz0w5blqezzcakp0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fak9gyz0w5blqezzcakp0.webp" alt="Building the tools in the Skipper Flipper GUI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target hardware: ESP32-S3 only&lt;/strong&gt; for the host ("master"). &lt;strong&gt;Status (29 October 2026): mostly working, needs tuning and proper testing.&lt;/strong&gt; On real hardware (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/HARDWARE_LOG.md" rel="noopener noreferrer"&gt;docs/HARDWARE_LOG.md&lt;/a&gt;), installed in a WEEDO Tina 2 with a BTT SKR Pico running plain Klipper v0.13.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the WiFi pages, console and &lt;code&gt;printer.cfg&lt;/code&gt; editor; firmware updates over WiFi;&lt;/li&gt;
&lt;li&gt;the Klipper link, clock sync, board configuration and recovery from board power cycles;&lt;/li&gt;
&lt;li&gt;temperatures, heating, PID tuning and Klipper's heater safety checks;&lt;/li&gt;
&lt;li&gt;the motor test, TMC2209 set-up, stallGuard and &lt;strong&gt;full homing of X, Y and Z&lt;/strong&gt; (2026-10-27);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a whole print file, run end to end&lt;/strong&gt; (2026-10-27): in mid-air, because the Tina 2's flexible Z gave no reliable stop to set a Z offset from;&lt;/li&gt;
&lt;li&gt;the micro SD card, the little OLED, touch buttons through the case, the hotend fan;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the print benchmark&lt;/strong&gt; (2026-10-29): 12 layers of a print path (9,617 lines, about a million steps) through the whole motion pipeline and the real board, drivers off, in 121.8 s, with the ESP32's printer task busy about 44 % of one core, of which about 14 % is real work and the rest mostly waiting for the board's acknowledgements (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/BENCHMARK.md" rel="noopener noreferrer"&gt;docs/BENCHMARK.md&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;integer motion maths&lt;/strong&gt; (INT32/64 test build, 2026-10-29): input shaping with pressure advance costs 10 % of one core at 100 mm/s instead of 19 %; the shaping work itself is about 3.7 times faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not yet:&lt;/strong&gt; a real printed part; input shaping, pressure advance, bed mesh and the other tuning features on a moving printer; the part-cooling fan and the third fan output (possibly a broken wire). &lt;strong&gt;Next test bench:&lt;/strong&gt; a QIDI X-Smart 3 (CoreXY) with an SKR Pico; the Tina 2 is retired as the bench (no sharp Z stop, and its tiny Y motor overheats). The whole story, including every mistake: &lt;strong&gt;&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/paper/A_FRANKENSTEIN_EXPERIENCE.md" rel="noopener noreferrer"&gt;A Frankenstein Experience&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/paper/A_FRANKENSTEIN_EXPERIENCE.pdf" rel="noopener noreferrer"&gt;PDF&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;New features by date, and whether each has been on hardware, are in &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Klipper wire protocol (CRC16, VLQ ints, framing, resync, data dictionary)&lt;/td&gt;
&lt;td&gt;Unit-tested; &lt;strong&gt;works on hardware&lt;/strong&gt; (ESP32-S3 to SKR Pico)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESP32-S3 firmware (&lt;code&gt;firmware/esp32s3&lt;/code&gt;): WiFi access point, status page, console, &lt;code&gt;printer.cfg&lt;/code&gt; editor, test bench, printer page, the Klipper host's jobs (heaters, TMC2209, moves, homing, printing from flash). Shipped ready to flash&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;On hardware&lt;/strong&gt;: link, heating, PID, motor test, TMC2209, stallGuard, full homing (2026-10-27), a print file run in mid-air (2026-10-27), WiFi updates, the print and motion benchmarks (2026-10-29). Not yet: a real printed part&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;skr-pico-uart&lt;/code&gt; printer-board firmware (&lt;strong&gt;plain Klipper v0.13.0&lt;/strong&gt;, serial on UART0; the ESP32-S3 firmware is the only bespoke part)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Works on hardware&lt;/strong&gt;: link, heater, thermistor, steppers, TMC UART, DIAG pins (with the 2026-10-10 build, which also held the since-removed, never-used segment queue; &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/research/segq-retired.md" rel="noopener noreferrer"&gt;why it went&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion planner + G-code engine (lookahead, junction deviation, trapezoids, pressure advance, G-code state)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;On hardware&lt;/strong&gt; for homing, a print file in mid-air and the benchmarks; checked in simulation for exact positions, speed and acceleration limits. Never yet used for a real part&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other board presets (generic RP2040, Creality 4.2.x, RAMPS mega2560, Ender-3 1.1.x, USB SKR Pico)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Compile only&lt;/strong&gt;; never run on hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flasher app (&lt;code&gt;flasher.py&lt;/code&gt;): Slave tab (printer board) and Master tab (ESP32-S3)&lt;/td&gt;
&lt;td&gt;ESP32-S3 flashing &lt;strong&gt;used on hardware&lt;/strong&gt; on Linux Mint. Other paths (SD card, avrdude, Windows/macOS) tested with mocks only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firmware build tool (CLI + Slave tab "My own build")&lt;/td&gt;
&lt;td&gt;Works on Linux (sandbox and the author's Linux Mint laptop); Windows needs WSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mcu_probe&lt;/code&gt;, &lt;code&gt;fake_mcu.py&lt;/code&gt;, one-command setup, Tools window&lt;/td&gt;
&lt;td&gt;Working on Linux Mint. Probe tried on an SKR Pico over USB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real printing&lt;/td&gt;
&lt;td&gt;A print file &lt;strong&gt;ran end to end on the Tina 2&lt;/strong&gt; (2026-10-27) but in mid-air (Z offset not found); upload, print from flash or SD card, pause / resume / cancel are simulated. &lt;strong&gt;No real part printed yet&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Micro SD card (&lt;code&gt;firmware/esp32s3/main/sdcard.c&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Works on hardware&lt;/strong&gt; (2026-10-19: mount, file list, upload; printing from it not yet tried): SPI card for big G-code files, file list / print / delete on the Printer page, &lt;code&gt;SDCARD_PRINT_FILE FILENAME=&lt;/code&gt;, M20/M23/M24 (the command side is unit-tested). See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/SD_CARD.md" rel="noopener noreferrer"&gt;docs/SD_CARD.md&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buttons on the case (&lt;code&gt;firmware/esp32s3/main/panel.cpp&lt;/code&gt;, &lt;code&gt;host/src/panel.cpp&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Touch works on hardware&lt;/strong&gt; (2026-10-26: Preheat and Home from staple pads melted into the Tina 2's case); up to four touch pads (bolts on wires) or switches: Preheat, Home and E-stop (both on a 1 s hold), Unload, and more. CAPTAIN and SKIPPER. See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/BUTTONS.md" rel="noopener noreferrer"&gt;docs/BUTTONS.md&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tuning (&lt;code&gt;host/src/shaping.cpp&lt;/code&gt;, &lt;code&gt;resonance.cpp&lt;/code&gt;, &lt;code&gt;bedmesh.cpp&lt;/code&gt;, &lt;code&gt;machine_pro.cpp&lt;/code&gt;; Tune page)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Built and PC-tested; the shaping and pressure-advance maths timed on the ESP32 (2026-10-29), not yet used on a moving printer&lt;/strong&gt; (2026-10-24): input shaping, pressure advance, accelerometer search and shaper calibration (matches Klipper's), bed mesh, screw tilt, arcs, G10/G11, exclude object, tuning tower. USB accelerometer: compile-only. CAPTAIN and SKIPPER only. See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/TUNING.md" rel="noopener noreferrer"&gt;docs/TUNING.md&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Little OLED display (&lt;code&gt;firmware/esp32s3/main/oled.cpp&lt;/code&gt;, &lt;code&gt;components/ktft_oled&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Works on hardware&lt;/strong&gt; (2026-10-26: temperatures and the shutdown screen on a 0.96" OLED in the Tina 2); built 2026-10-23: SSD1306 / SSD1315 / SH1106, 128x64 or 128x32, on GPIO8/9: temperatures, progress bar, HEATING / HOMING / EXTRUDING / LOADING, "no board" with the WiFi name and password. See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/OLED.md" rel="noopener noreferrer"&gt;docs/OLED.md&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Three builds by board memory: DECKHAND / CAPTAIN / SKIPPER (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/TIERS.md" rel="noopener noreferrer"&gt;docs/TIERS.md&lt;/a&gt;), Flasher "Flash for my board"&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;CAPTAIN runs on hardware&lt;/strong&gt; (2026-10-20: picked by the Flasher, 2 MB PSRAM found and used); WiFi firmware updates work (2026-10-20); the important-events log works (2026-10-28); not yet tried there: bigger look-ahead with PSRAM, slicer upload (OctoPrint-style), home WiFi, thumbnails, print history, event log, printer.cfg backups, crash reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Print benchmark, motion benchmark, INT32/64 test build (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/BENCHMARK.md" rel="noopener noreferrer"&gt;docs/BENCHMARK.md&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;On hardware&lt;/strong&gt; (2026-10-28/29): both benchmarks run to the end on the Tina 2 with the SKR Pico and the drivers off; results in the important-events log. The integer build has not moved motors yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fans&lt;/td&gt;
&lt;td&gt;Hotend fan &lt;strong&gt;confirmed on hardware&lt;/strong&gt;; part-cooling fan and the third fan output not yet (possibly a broken wire, will update when tested)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total checks: 1,825 C++ unit checks (run twice: with double and with integer motion maths), the OLED layout tests and 192 Python tests (including power-cycle, stallGuard and printing runs against a simulated board), all passing in the sandbox. Passing tests mean the maths and bookkeeping are right, not that a printer will behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;The GUI building the tools and running the unit tests:&lt;/p&gt;

&lt;p&gt;The GUI running the probe against the built-in fake MCU (simulated, no hardware):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwwdyfovyfzcafenw0peq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwwdyfovyfzcafenw0peq.webp" alt="Running mcu_probe against the fake MCU"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both screenshots use the &lt;strong&gt;simulated&lt;/strong&gt; board, not real hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flash a board (Linux, macOS, Windows)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 flasher.py          (Windows: double-click Flasher.bat; macOS: Flasher.command)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need Python 3.8+ with Tk (Linux: &lt;code&gt;sudo apt install python3-tk&lt;/code&gt;; on Windows and macOS the installer from python.org includes Tk). No toolchain is needed: firmware for every board preset (five boards, plus a serial variant of the SKR Pico) ships in &lt;code&gt;firmware/prebuilt/&lt;/code&gt;. Pick the &lt;strong&gt;Slave&lt;/strong&gt; tab, choose your board, press &lt;strong&gt;Flash...&lt;/strong&gt; and confirm. RP2040 boards: hold BOOTSEL while plugging in USB. STM32 boards such as the Creality 4.2.x: put the SD card in this computer, &lt;strong&gt;Browse&lt;/strong&gt; to it, flash, then move the card to the printer and power-cycle it. AVR boards need &lt;code&gt;avrdude&lt;/code&gt; installed. The &lt;strong&gt;Master&lt;/strong&gt; tab flashes an ESP32-S3 with esptool (Install esptool, then &lt;strong&gt;Use shipped firmware&lt;/strong&gt;, pick the port, Flash). The port box fills itself with the ESP32-S3's port when it can tell which one it is (otherwise pick it). The shipped image starts a WiFi network &lt;code&gt;SkipperFlipper-XXXX&lt;/code&gt; (WPA2, default password &lt;code&gt;skipperflipper&lt;/code&gt;, change it before real use) with a page at &lt;code&gt;http://192.168.4.1/&lt;/code&gt;, a live log at &lt;code&gt;/console&lt;/code&gt; and &lt;code&gt;printer.cfg&lt;/code&gt; at &lt;code&gt;/config&lt;/code&gt;; it talks Klipper to the printer board over UART (identify, clock sync, &lt;code&gt;printer.cfg&lt;/code&gt;, heaters, TMC2209 drivers, motor tests at &lt;code&gt;/test&lt;/code&gt;, homing and running a print file from the Printer page: all on hardware with the SKR Pico; a real printed part not yet). Logs appear on the chip's native USB port. &lt;strong&gt;ESP32-S3 only&lt;/strong&gt;: other ESP32 chips (original, S2, C3) are not supported, and esptool refuses to write if it finds a different chip. You can also flash your own &lt;code&gt;.bin&lt;/code&gt;. To connect the ESP32-S3 to an SKR Pico with four jumper wires, see &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/WIRING_SKR_PICO.md" rel="noopener noreferrer"&gt;docs/WIRING_SKR_PICO.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdxsgu81xrdozf74pqjjp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdxsgu81xrdozf74pqjjp.webp" alt="The Flasher, Slave tab with the Creality 4.2.x preset"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only the &lt;code&gt;skr-pico-uart&lt;/code&gt; firmware (SKR Pico) has run on a board&lt;/strong&gt;, where it has done everything above; the other presets have never been flashed. Read &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/FIRMWARE.md" rel="noopener noreferrer"&gt;docs/FIRMWARE.md&lt;/a&gt; first, and do not flash a printer you cannot recover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start (Linux Mint / Ubuntu / Debian)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/The-Dorkknight/esp-printhost
&lt;span class="nb"&gt;cd &lt;/span&gt;esp-printhost
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 printhost.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one command installs what is missing (build tools, cmake, zlib, python3-tk, acl), gives you access to USB serial ports without logging out, stops ModemManager and brltty from grabbing printer boards, builds &lt;code&gt;mcu_probe&lt;/code&gt;, runs the tests, and opens the GUI as your normal user. It is safe to re-run. &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/HOW_IT_WORKS.md" rel="noopener noreferrer"&gt;HOW_IT_WORKS.md&lt;/a&gt; lists exactly what it changes on your system.&lt;/p&gt;

&lt;p&gt;Options: &lt;code&gt;--check&lt;/code&gt; (report only, change nothing), &lt;code&gt;--no-gui&lt;/code&gt;, &lt;code&gt;--clean&lt;/code&gt;, &lt;code&gt;--yes&lt;/code&gt;, &lt;code&gt;--version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If anything fails it prints the path of a log file in &lt;code&gt;~/esp-printhost-logs/&lt;/code&gt;. Attach that when you open an issue.&lt;/p&gt;

&lt;p&gt;macOS: the C++ library and tests build (&lt;code&gt;python3 host/tools/build.py&lt;/code&gt;), but &lt;code&gt;mcu_probe&lt;/code&gt; is Linux-only (it uses Linux's &lt;code&gt;termios2&lt;/code&gt; for 250000 baud) and the one-command setup is Linux-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the GUI
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build tools&lt;/strong&gt; compiles everything and runs the unit tests. Output is colour-coded per tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fake MCU: Start&lt;/strong&gt; runs a pretend Klipper board (no hardware). Its port appears in the Port box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run probe&lt;/strong&gt; connects, reads the board's pin list into the &lt;strong&gt;Blink pin&lt;/strong&gt; dropdown, and prints uptime and state. Tick &lt;strong&gt;Blink pin&lt;/strong&gt; and pick a pin to also toggle it 10 times.&lt;/li&gt;
&lt;li&gt;The output box is selectable (Ctrl+C, right-click, &lt;strong&gt;Copy all&lt;/strong&gt;, &lt;strong&gt;Save as...&lt;/strong&gt;). Everything is also logged to &lt;code&gt;~/esp-printhost-logs/&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it without hardware
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 host/tools/fake_mcu.py            &lt;span class="c"&gt;# prints /dev/pts/N&lt;/span&gt;
./host/build/mcu_probe /dev/pts/N 250000 &lt;span class="nt"&gt;--pins&lt;/span&gt; &lt;span class="nt"&gt;--blink&lt;/span&gt; PA5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try it on a real board
&lt;/h2&gt;

&lt;p&gt;You need a board running &lt;strong&gt;stock Klipper MCU firmware&lt;/strong&gt; (the Flasher ships plain Klipper v0.13.0 builds in &lt;code&gt;firmware/prebuilt/&lt;/code&gt;, or build your own with the Firmware tab). Stop Klipper or anything else using the port, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./host/build/mcu_probe /dev/serial/by-id/&amp;lt;your-board&amp;gt; 250000 &lt;span class="nt"&gt;--pins&lt;/span&gt; &lt;span class="nt"&gt;--blink&lt;/span&gt; &amp;lt;pin&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--pins&lt;/code&gt; lists the pin names the board accepts. The probe validates your pin name before touching the board, and if the board is already configured it &lt;strong&gt;resets it&lt;/strong&gt; first (the USB device re-appears after about two seconds). Do not run it on a board that is in the middle of a print.&lt;/p&gt;

&lt;p&gt;Tested so far: BTT SKR Pico (RP2040, Klipper v0.10.0-181). The probe identified it, read 31 pin names, reset it, configured &lt;code&gt;gpio24&lt;/code&gt; and sent 10 toggles with 0 CRC errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It has not printed a real part yet.&lt;/strong&gt; Homing and a whole print file have run on the Tina 2, but in mid-air: first layer, Z offset, extrusion and cooling are untested in practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heaters: stay with the printer.&lt;/strong&gt; Klipper's heater safety is ported and has shut the board down correctly on hardware ("ADC out of range"), but this is experimental firmware: keep a hand near the power switch and never leave it running unattended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tuning features have not moved a printer&lt;/strong&gt;: input shaping, pressure advance, accelerometer calibration, bed mesh, screw tilt, arcs, firmware retraction, exclude object and tuning tower are built and PC-tested; the shaping maths has been timed on the ESP32, nothing more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The INT32/64 test build&lt;/strong&gt; has passed the PC tests (within one step of the &lt;code&gt;double&lt;/code&gt; maths everywhere) and both benchmarks on the ESP32, but has not driven motors with its integer shaper yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sending waits for the board&lt;/strong&gt;: about 30 % of one core in the print benchmark is the ESP32 waiting for the SKR Pico's acknowledgements, where Klipper keeps sending. Fine at the Tina 2's speeds; it is the next speed work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only the SKR Pico has run.&lt;/strong&gt; The other board presets are compile-only, and their bootloader details are assumptions. Small 8-bit boards give Klipper a much smaller move queue than the SKR Pico's 1,024 commands; this host has not been tried with one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flash is nearly full&lt;/strong&gt; on DECKHAND (about 6 KB free) and CAPTAIN (about 14 KB); new features may need something trimmed.&lt;/li&gt;
&lt;li&gt;The part-cooling fan and the third fan output are not confirmed (possibly a broken wire). The cause of one early SKR Pico lock-up after a shutdown (2026-10-13) is still unknown.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcu_probe&lt;/code&gt; is Linux-only.&lt;/li&gt;
&lt;li&gt;The GitHub workflows in &lt;code&gt;.github/workflows/&lt;/code&gt; have not been checked for this release.&lt;/li&gt;
&lt;li&gt;Presets for boards with clone chips (some Creality 4.2.x) are flagged unverified; check your chip.&lt;/li&gt;
&lt;li&gt;Design documents describe intent, not finished work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project layout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printhost.py          one-command setup + launcher
flasher.py            the Flasher window (also Flasher.bat / Flasher.command)
HOW_IT_WORKS.md       what it reads, changes and never does
docs/                 DESIGN, PROTOCOL, ENGINE, FIRMWARE, TUNING, TASKS, project page
firmware/esp32s3/       ESP32-S3 firmware (ESP-IDF project) + build.sh
firmware/prebuilt-esp32s3/  shipped ESP32-S3 images (DECKHAND / CAPTAIN / SKIPPER; test-int32/ for the INT32/64 test build)
firmware/boards/      board presets (.config + .json)
firmware/prebuilt/    shipped plain-Klipper firmware per board (+ pin tables); refresh with skipper_fw.py bundle
host/                 portable C++17 core (the same code runs on the PC and, as an ESP-IDF component, on the ESP32-S3)
  include/ph/         wire, msgproto, json_mini, config_ini, gcode, motion, planner, engine
  src/  test/         implementation + 1,825 unit checks
  tools/              build.py, skipper_fw.py, printhost_gui.py (+ fw_panel, skipper_theme),
                      mcu_probe.cpp, fake_mcu.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/TASKS.md" rel="noopener noreferrer"&gt;docs/TASKS.md&lt;/a&gt;. Next, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the integer input shaper the standard in every build (keeping the &lt;code&gt;double&lt;/code&gt; planner), now that it measures about 3.7 times faster on the ESP32.&lt;/li&gt;
&lt;li&gt;Send without waiting for each acknowledgement, the way Klipper does, to win back most of the 30 % of a core spent waiting.&lt;/li&gt;
&lt;li&gt;Move to the QIDI X-Smart 3 test bench and print a first real part.&lt;/li&gt;
&lt;li&gt;Try the tuning features (input shaping, pressure advance, bed mesh) on a moving printer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The look is a 1928 black-and-white cartoon (visuals only).&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/esp-printhost" rel="noopener noreferrer"&gt;The-Dorkknight/esp-printhost on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Skipper Flipper</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Thu, 29 Oct 2026 23:30:03 +0000</pubDate>
      <link>https://dev.to/open-madscience/skipper-flipper-4kbc</link>
      <guid>https://dev.to/open-madscience/skipper-flipper-4kbc</guid>
      <description>&lt;p&gt;&lt;em&gt;An experimental ESP32 pseudo Klipper port.&lt;/em&gt; (Repository and package name: &lt;code&gt;esp-printhost&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPL-3.0&lt;/strong&gt; · Linux (Mint / Ubuntu) · C++17 core + Python tools · &lt;a href="https://The-Dorkknight.github.io/esp-printhost/" rel="noopener noreferrer"&gt;Project page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Licensed under the GNU General Public License v3.0, see &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/LICENSE" rel="noopener noreferrer"&gt;LICENSE&lt;/a&gt;. &lt;strong&gt;No warranty and no liability:&lt;/strong&gt; this is provided as is. You use it at your own risk, on your own hardware, and the author is not responsible for damage to boards, printers or anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is
&lt;/h2&gt;

&lt;p&gt;An idea for reviving old, donated 3D printers: a cheap ESP32-S3 becomes the "brain" (WiFi, web UI, text config, motion planning) and the printer's original board becomes a simple slave running a modified Klipper MCU firmware. The full plan is in &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/DESIGN.md" rel="noopener noreferrer"&gt;docs/DESIGN.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target hardware: ESP32-S3 only&lt;/strong&gt; for the host ("master"). &lt;strong&gt;Status: mostly working, needs tuning and proper testing.&lt;/strong&gt; On real hardware (see &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/HARDWARE_LOG.md" rel="noopener noreferrer"&gt;docs/HARDWARE_LOG.md&lt;/a&gt;), installed in a WEEDO Tina 2 with a BTT SKR Pico: the WiFi status page, console and &lt;code&gt;printer.cfg&lt;/code&gt; editor; the Klipper link, clock sync, board configuration and recovery from board power cycles; temperatures, heating, PID tuning and Klipper's heater safety checks; the motor test; TMC2209 set-up; stallGuard triggering; the hotend fan; a micro SD card for big G-code files (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/SD_CARD.md" rel="noopener noreferrer"&gt;docs/SD_CARD.md&lt;/a&gt;: mounts, lists, uploads). &lt;strong&gt;Not yet confirmed:&lt;/strong&gt; the part-cooling fan and the third fan output (possibly a broken wire; will update when tested). &lt;strong&gt;Needs tuning:&lt;/strong&gt; stallGuard threshold, motor current, speeds, &lt;code&gt;rotation_distance&lt;/code&gt;. &lt;strong&gt;Only simulated so far:&lt;/strong&gt; moves, full homing and probing, and printing a file. &lt;strong&gt;New (2026-10-20), not yet on hardware:&lt;/strong&gt; three builds by board memory with a one-click Flasher choice (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/TIERS.md" rel="noopener noreferrer"&gt;docs/TIERS.md&lt;/a&gt;), slicer uploads, home WiFi and crash reports. &lt;strong&gt;New (2026-10-23), not yet on hardware:&lt;/strong&gt; a cheap I2C OLED shows temperatures, print progress and what the printer is doing (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/OLED.md" rel="noopener noreferrer"&gt;docs/OLED.md&lt;/a&gt;). &lt;strong&gt;New (2026-10-24), not yet on hardware:&lt;/strong&gt; Klipper's input shaping and pressure advance, automatic input shaper calibration with an accelerometer it finds by itself, bed mesh with a coloured heightmap, screw tilt, arcs, firmware retraction, exclude object and tuning tower on CAPTAIN and SKIPPER (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/TUNING.md" rel="noopener noreferrer"&gt;docs/TUNING.md&lt;/a&gt;). &lt;strong&gt;New (2026-10-27), not yet on hardware:&lt;/strong&gt; CoreXY, Z homing on a probe, &lt;code&gt;gear_ratio&lt;/code&gt;, a chamber thermistor on the ESP32 (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/CHAMBER_SENSOR.md" rel="noopener noreferrer"&gt;docs/CHAMBER_SENSOR.md&lt;/a&gt;) and a QIDI X-Smart 3 test-bench config (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/configs/qidi-xsmart3-skr-pico.md" rel="noopener noreferrer"&gt;docs/configs/qidi-xsmart3-skr-pico.md&lt;/a&gt;). &lt;strong&gt;New (2026-10-28), not yet on hardware:&lt;/strong&gt; a print benchmark that times every stage of the motion pipeline with nothing moving, an int32/int64 motion test build for CAPTAIN, and a log of important events (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/BENCHMARK.md" rel="noopener noreferrer"&gt;docs/BENCHMARK.md&lt;/a&gt;). The whole story, including every mistake: &lt;strong&gt;&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/paper/A_FRANKENSTEIN_EXPERIENCE.md" rel="noopener noreferrer"&gt;A Frankenstein Experience&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/paper/A_FRANKENSTEIN_EXPERIENCE.pdf" rel="noopener noreferrer"&gt;PDF&lt;/a&gt;).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Klipper wire protocol (CRC16, VLQ ints, framing, resync, data dictionary)&lt;/td&gt;
&lt;td&gt;Unit-tested; &lt;strong&gt;works on hardware&lt;/strong&gt; (ESP32-S3 to SKR Pico)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESP32-S3 firmware (&lt;code&gt;firmware/esp32s3&lt;/code&gt;): WiFi access point, status page, console, &lt;code&gt;printer.cfg&lt;/code&gt; editor, test bench, printer page, the Klipper host's jobs (heaters, TMC2209, moves, homing, printing from flash). Shipped ready to flash&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;On hardware&lt;/strong&gt; (to 2026-10-14): link, heating, PID, motor test, TMC2209, stallGuard trigger. Moves, homing and printing: simulation only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;skr-pico-uart&lt;/code&gt; printer-board firmware (&lt;strong&gt;plain Klipper v0.13.0&lt;/strong&gt;, serial on UART0; the ESP32-S3 firmware is the only bespoke part)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Works on hardware&lt;/strong&gt;: link, heater, thermistor, steppers, TMC UART, DIAG pins (with the 2026-10-10 build, which also held the since-removed, never-used segment queue; &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/research/segq-retired.md" rel="noopener noreferrer"&gt;why it went&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion planner + G-code engine (lookahead, junction deviation, trapezoids, pressure advance, G-code state)&lt;/td&gt;
&lt;td&gt;Working in simulation: output is run through the real core and checked for exact positions, speed and acceleration limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other board presets (generic RP2040, Creality 4.2.x, RAMPS mega2560, Ender-3 1.1.x, USB SKR Pico)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Compile only&lt;/strong&gt;; never run on hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flasher app (&lt;code&gt;flasher.py&lt;/code&gt;): Slave tab (printer board) and Master tab (ESP32-S3)&lt;/td&gt;
&lt;td&gt;ESP32-S3 flashing &lt;strong&gt;used on hardware&lt;/strong&gt; on Linux Mint. Other paths (SD card, avrdude, Windows/macOS) tested with mocks only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firmware build tool (CLI + Slave tab "My own build")&lt;/td&gt;
&lt;td&gt;Works on Linux (sandbox and the author's Linux Mint laptop); Windows needs WSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mcu_probe&lt;/code&gt;, &lt;code&gt;fake_mcu.py&lt;/code&gt;, one-command setup, Tools window&lt;/td&gt;
&lt;td&gt;Working on Linux Mint. Probe tried on an SKR Pico over USB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real printing&lt;/td&gt;
&lt;td&gt;Built and &lt;strong&gt;simulated only&lt;/strong&gt; (upload, print from flash, pause / resume / cancel); not yet run on the printer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Micro SD card (&lt;code&gt;firmware/esp32s3/main/sdcard.c&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Works on hardware&lt;/strong&gt; (2026-10-19: mount, file list, upload; printing from it not yet tried): SPI card for big G-code files, file list / print / delete on the Printer page, &lt;code&gt;SDCARD_PRINT_FILE FILENAME=&lt;/code&gt;, M20/M23/M24 (the command side is unit-tested). See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/SD_CARD.md" rel="noopener noreferrer"&gt;docs/SD_CARD.md&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buttons on the case (&lt;code&gt;firmware/esp32s3/main/panel.cpp&lt;/code&gt;, &lt;code&gt;host/src/panel.cpp&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Touch works on hardware&lt;/strong&gt; (2026-10-26: Preheat and Home from staple pads melted into the Tina 2's case); up to four touch pads (bolts on wires) or switches: Preheat, Home and E-stop (both on a 1 s hold), Unload, and more. CAPTAIN and SKIPPER. See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/BUTTONS.md" rel="noopener noreferrer"&gt;docs/BUTTONS.md&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tuning (&lt;code&gt;host/src/shaping.cpp&lt;/code&gt;, &lt;code&gt;resonance.cpp&lt;/code&gt;, &lt;code&gt;bedmesh.cpp&lt;/code&gt;, &lt;code&gt;machine_pro.cpp&lt;/code&gt;; Tune page)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Built, PC-tested and run in QEMU, not yet on a printer&lt;/strong&gt; (2026-10-24): input shaping, pressure advance, accelerometer search and shaper calibration (matches Klipper's), bed mesh, screw tilt, arcs, G10/G11, exclude object, tuning tower. USB accelerometer: compile-only. CAPTAIN and SKIPPER only. See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/TUNING.md" rel="noopener noreferrer"&gt;docs/TUNING.md&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Little OLED display (&lt;code&gt;firmware/esp32s3/main/oled.cpp&lt;/code&gt;, &lt;code&gt;components/ktft_oled&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Works on hardware&lt;/strong&gt; (2026-10-26: temperatures and the shutdown screen on a 0.96" OLED in the Tina 2); built 2026-10-23: SSD1306 / SSD1315 / SH1106, 128x64 or 128x32, on GPIO8/9: temperatures, progress bar, HEATING / HOMING / EXTRUDING / LOADING, "no board" with the WiFi name and password. See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/OLED.md" rel="noopener noreferrer"&gt;docs/OLED.md&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Three builds by board memory: DECKHAND / CAPTAIN / SKIPPER (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/TIERS.md" rel="noopener noreferrer"&gt;docs/TIERS.md&lt;/a&gt;), Flasher "Flash for my board"&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;CAPTAIN runs on hardware&lt;/strong&gt; (2026-10-20: picked by the Flasher, 2 MB PSRAM found and used); not yet tried there: WiFi firmware updates, bigger look-ahead with PSRAM, slicer upload (OctoPrint-style), home WiFi, thumbnails, print history, event log, printer.cfg backups, crash reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fans&lt;/td&gt;
&lt;td&gt;Hotend fan &lt;strong&gt;confirmed on hardware&lt;/strong&gt;; part-cooling fan and the third fan output not yet (possibly a broken wire, will update when tested)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total checks: 1,825 C++ unit checks (run twice: with double and with integer motion maths), the OLED layout tests and 190 Python tests (including power-cycle, stallGuard and printing runs against a simulated board), all passing in the sandbox. Passing tests mean the maths and bookkeeping are right, not that a printer will behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;The GUI building the tools and running the unit tests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fak9gyz0w5blqezzcakp0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fak9gyz0w5blqezzcakp0.webp" alt="Building the tools in the Skipper Flipper GUI" width="799" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The GUI running the probe against the built-in fake MCU (simulated, no hardware):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwwdyfovyfzcafenw0peq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwwdyfovyfzcafenw0peq.webp" alt="Running mcu_probe against the fake MCU" width="799" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both screenshots use the &lt;strong&gt;simulated&lt;/strong&gt; board, not real hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flash a board (Linux, macOS, Windows)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 flasher.py          (Windows: double-click Flasher.bat; macOS: Flasher.command)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need Python 3.8+ with Tk (Linux: &lt;code&gt;sudo apt install python3-tk&lt;/code&gt;; on Windows and macOS the installer from python.org includes Tk). No toolchain is needed: firmware for every board preset (five boards, plus a serial variant of the SKR Pico) ships in &lt;code&gt;firmware/prebuilt/&lt;/code&gt;. Pick the &lt;strong&gt;Slave&lt;/strong&gt; tab, choose your board, press &lt;strong&gt;Flash...&lt;/strong&gt; and confirm. RP2040 boards: hold BOOTSEL while plugging in USB. STM32 boards such as the Creality 4.2.x: put the SD card in this computer, &lt;strong&gt;Browse&lt;/strong&gt; to it, flash, then move the card to the printer and power-cycle it. AVR boards need &lt;code&gt;avrdude&lt;/code&gt; installed. The &lt;strong&gt;Master&lt;/strong&gt; tab flashes an ESP32-S3 with esptool (Install esptool, then &lt;strong&gt;Use shipped firmware&lt;/strong&gt;, pick the port, Flash). The port box fills itself with the ESP32-S3's port when it can tell which one it is (otherwise pick it). The shipped image starts a WiFi network &lt;code&gt;SkipperFlipper-XXXX&lt;/code&gt; (WPA2, default password &lt;code&gt;skipperflipper&lt;/code&gt;, change it before real use) with a page at &lt;code&gt;http://192.168.4.1/&lt;/code&gt;, a live log at &lt;code&gt;/console&lt;/code&gt; and &lt;code&gt;printer.cfg&lt;/code&gt; at &lt;code&gt;/config&lt;/code&gt;; it talks Klipper to the printer board over UART (identify, clock sync, LEDs and thermistors from &lt;code&gt;printer.cfg&lt;/code&gt;, confirmed on hardware; heaters, fans, TMC2209 drivers and motor tests from the test bench at &lt;code&gt;/test&lt;/code&gt;, simulated only so far). Moves, homing and printing a file (Printer page at &lt;code&gt;/printer&lt;/code&gt;) are built and simulated, not yet run on hardware. Logs appear on the chip's native USB port. &lt;strong&gt;ESP32-S3 only&lt;/strong&gt;: other ESP32 chips (original, S2, C3) are not supported, and esptool refuses to write if it finds a different chip. You can also flash your own &lt;code&gt;.bin&lt;/code&gt;. To connect the ESP32-S3 to an SKR Pico with four jumper wires, see &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/WIRING_SKR_PICO.md" rel="noopener noreferrer"&gt;docs/WIRING_SKR_PICO.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdxsgu81xrdozf74pqjjp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdxsgu81xrdozf74pqjjp.webp" alt="The Flasher, Slave tab with the Creality 4.2.x preset" width="800" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only the &lt;code&gt;skr-pico-uart&lt;/code&gt; firmware has run on a board, and only for the link and its on-board LED&lt;/strong&gt;; the other presets have never been flashed. Read &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/FIRMWARE.md" rel="noopener noreferrer"&gt;docs/FIRMWARE.md&lt;/a&gt; first, and do not flash a printer you cannot recover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start (Linux Mint / Ubuntu / Debian)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/The-Dorkknight/esp-printhost
&lt;span class="nb"&gt;cd &lt;/span&gt;esp-printhost
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 printhost.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one command installs what is missing (build tools, cmake, zlib, python3-tk, acl), gives you access to USB serial ports without logging out, stops ModemManager and brltty from grabbing printer boards, builds &lt;code&gt;mcu_probe&lt;/code&gt;, runs the tests, and opens the GUI as your normal user. It is safe to re-run. &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/HOW_IT_WORKS.md" rel="noopener noreferrer"&gt;HOW_IT_WORKS.md&lt;/a&gt; lists exactly what it changes on your system.&lt;/p&gt;

&lt;p&gt;Options: &lt;code&gt;--check&lt;/code&gt; (report only, change nothing), &lt;code&gt;--no-gui&lt;/code&gt;, &lt;code&gt;--clean&lt;/code&gt;, &lt;code&gt;--yes&lt;/code&gt;, &lt;code&gt;--version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If anything fails it prints the path of a log file in &lt;code&gt;~/esp-printhost-logs/&lt;/code&gt;. Attach that when you open an issue.&lt;/p&gt;

&lt;p&gt;macOS: the C++ library and tests build (&lt;code&gt;python3 host/tools/build.py&lt;/code&gt;), but &lt;code&gt;mcu_probe&lt;/code&gt; is Linux-only (it uses Linux's &lt;code&gt;termios2&lt;/code&gt; for 250000 baud) and the one-command setup is Linux-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the GUI
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build tools&lt;/strong&gt; compiles everything and runs the unit tests. Output is colour-coded per tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fake MCU: Start&lt;/strong&gt; runs a pretend Klipper board (no hardware). Its port appears in the Port box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run probe&lt;/strong&gt; connects, reads the board's pin list into the &lt;strong&gt;Blink pin&lt;/strong&gt; dropdown, and prints uptime and state. Tick &lt;strong&gt;Blink pin&lt;/strong&gt; and pick a pin to also toggle it 10 times.&lt;/li&gt;
&lt;li&gt;The output box is selectable (Ctrl+C, right-click, &lt;strong&gt;Copy all&lt;/strong&gt;, &lt;strong&gt;Save as...&lt;/strong&gt;). Everything is also logged to &lt;code&gt;~/esp-printhost-logs/&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it without hardware
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 host/tools/fake_mcu.py            &lt;span class="c"&gt;# prints /dev/pts/N&lt;/span&gt;
./host/build/mcu_probe /dev/pts/N 250000 &lt;span class="nt"&gt;--pins&lt;/span&gt; &lt;span class="nt"&gt;--blink&lt;/span&gt; PA5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try it on a real board
&lt;/h2&gt;

&lt;p&gt;You need a board running &lt;strong&gt;stock Klipper MCU firmware&lt;/strong&gt; (build it from Klipper with &lt;code&gt;make menuconfig&lt;/code&gt;; this project does not ship firmware yet). Stop Klipper or anything else using the port, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./host/build/mcu_probe /dev/serial/by-id/&amp;lt;your-board&amp;gt; 250000 &lt;span class="nt"&gt;--pins&lt;/span&gt; &lt;span class="nt"&gt;--blink&lt;/span&gt; &amp;lt;pin&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--pins&lt;/code&gt; lists the pin names the board accepts. The probe validates your pin name before touching the board, and if the board is already configured it &lt;strong&gt;resets it&lt;/strong&gt; first (the USB device re-appears after about two seconds). Do not run it on a board that is in the middle of a print.&lt;/p&gt;

&lt;p&gt;Tested so far: BTT SKR Pico (RP2040, Klipper v0.10.0-181). The probe identified it, read 31 pin names, reset it, configured &lt;code&gt;gpio24&lt;/code&gt; and sent 10 toggles with 0 CRC errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause and fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/dev/ttyUSB0&lt;/code&gt; disappears when an Ender / Creality board is plugged in&lt;/td&gt;
&lt;td&gt;brltty grabbing the CH340 chip. Re-run &lt;code&gt;sudo python3 printhost.py&lt;/code&gt; and say yes to disabling it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Permission denied&lt;/code&gt; on the port&lt;/td&gt;
&lt;td&gt;Not in &lt;code&gt;dialout&lt;/code&gt; yet. &lt;code&gt;printhost.py&lt;/code&gt; fixes today's session; log out and in for the long term&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Device or resource busy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Klipper, Cura or a serial monitor has the port open. Close it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no identify_response&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wrong port or baud, board is not running Klipper firmware, or it needs a power cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;already configured&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The probe resets it automatically; if that fails, power-cycle the board&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GUI will not open&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sudo apt install python3-tk&lt;/code&gt;, or run &lt;code&gt;python3 printhost.py --no-gui&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Probe says OK but nothing lights up&lt;/td&gt;
&lt;td&gt;See Known limitations
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Known limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only the link and the SKR Pico's LED have run on hardware&lt;/strong&gt; (ESP32-S3 and SKR Pico); clock sync, &lt;code&gt;printer.cfg&lt;/code&gt; and the &lt;code&gt;SET_LED&lt;/code&gt; console also ran on it for about 41 minutes (docs/HARDWARE_LOG.md); the reset-and-reconfigure after editing &lt;code&gt;printer.cfg&lt;/code&gt; has not been tried on hardware. Bootloader details for the other boards are assumptions that compile but are untested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heater, fan and motor control is new and has only run in simulation&lt;/strong&gt; (&lt;code&gt;fake_mcu.py&lt;/code&gt; and unit tests; see docs/TEST_BENCH.md). It follows Klipper's heater safety (board-side &lt;code&gt;max_duration&lt;/code&gt; and ADC range checks, &lt;code&gt;verify_heater&lt;/code&gt;, emergency stop), but has not been tried on real hardware: stay with the printer, keep a hand on the power switch, and do not rely on it unattended. Moves, homing and printing are newer still and have only run in simulation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin toggle is not physically confirmed.&lt;/strong&gt; On the SKR Pico the test used &lt;code&gt;gpio24&lt;/code&gt;, which (as far as we can tell from BTT's pin map, not verified) is the NeoPixel/RGB header. A NeoPixel needs a timed data stream, so a plain on/off will not light one. The protocol side worked; try a pin with a visible LED or a multimeter on it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;finalize_config&lt;/code&gt; is sent with a fixed placeholder CRC, which is fine for stock firmware but is not a real config checksum yet.&lt;/li&gt;
&lt;li&gt;The ESP-IDF build of the library is untested; only the PC build is exercised. The planner emits about one segment per tiny move, so link bandwidth for very fine G-code is unmeasured.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcu_probe&lt;/code&gt; is Linux-only.&lt;/li&gt;
&lt;li&gt;The GitHub workflows in &lt;code&gt;.github/workflows/&lt;/code&gt; have not been run yet.&lt;/li&gt;
&lt;li&gt;Presets for boards with clone chips (some Creality 4.2.x) are flagged unverified; check your chip.&lt;/li&gt;
&lt;li&gt;Design documents describe intent, not finished work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project layout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printhost.py          one-command setup + launcher
flasher.py            the Flasher window (also Flasher.bat / Flasher.command)
HOW_IT_WORKS.md       what it reads, changes and never does
docs/                 DESIGN, PROTOCOL, ENGINE, FIRMWARE, TUNING, TASKS, project page
firmware/esp32s3/       ESP32-S3 firmware (ESP-IDF project) + build.sh
firmware/prebuilt-esp32s3/  shipped ESP32-S3 image
firmware/boards/      board presets (.config + .json)
firmware/prebuilt/    shipped plain-Klipper firmware per board (+ pin tables); refresh with skipper_fw.py bundle
host/                 portable C++17 core (also an ESP-IDF component, untested)
  include/ph/         wire, msgproto, json_mini, config_ini, gcode, motion, planner, engine
  src/  test/         implementation + 480 unit tests
  tools/              build.py, skipper_fw.py, printhost_gui.py (+ fw_panel, skipper_theme),
                      mcu_probe.cpp, fake_mcu.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/TASKS.md" rel="noopener noreferrer"&gt;docs/TASKS.md&lt;/a&gt;. Next: real Klipper behaviour on the ESP32-S3, in stages: clock sync, configuring the board from &lt;code&gt;printer.cfg&lt;/code&gt;, a harmless first action (LED), temperatures, then heaters with safety checks and motor tests (built, waiting for the hardware test), then real moves and homing, then printing a file. The look is a 1928 black-and-white cartoon (visuals only); a Benchy-themed pass is planned for when real building starts.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/esp-printhost" rel="noopener noreferrer"&gt;The-Dorkknight/esp-printhost on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>iot</category>
      <category>python</category>
      <category>cpp</category>
      <category>hardware</category>
    </item>
    <item>
      <title>SoloTrace</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Thu, 29 Oct 2026 23:21:56 +0000</pubDate>
      <link>https://dev.to/open-madscience/solotrace-24ml</link>
      <guid>https://dev.to/open-madscience/solotrace-24ml</guid>
      <description>&lt;p&gt;An easy PCB designer for &lt;strong&gt;single-sided, through-hole, quick-prototype boards&lt;/strong&gt;. A small library of real full-size parts, traces up to 10 mm wide set with a slider, &lt;strong&gt;every corner chamfered&lt;/strong&gt; (no pure 90° traces), an autorouter that avoids the "tangled string" look, and output for printing (toner transfer), Gerber fabs and a &lt;strong&gt;gantry diode laser (Elegoo Phecda) with a negative option and adjustable trace gap&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Screenshots are of the real program running on a Linux test machine with the built-in example boards.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/docs/images/gui-routed.webp" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/docs%2Fimages%2Fgui-routed.webp" alt="SoloTrace main window with a routed 555 flasher" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Licence and no warranty.&lt;/strong&gt; Copyright Matthew Armstrong. Licensed under the &lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/LICENSE.md" rel="noopener noreferrer"&gt;PolyForm Noncommercial License 1.0.0&lt;/a&gt;: free to use, change and share for non-commercial purposes; commercial use needs separate permission. It is &lt;strong&gt;source-available, not open source&lt;/strong&gt;. GitHub shows the licence as "Other". This is not legal advice. The software is provided &lt;strong&gt;as is, with no warranty and no liability&lt;/strong&gt;. Third-party parts keep their own licences (&lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/THIRD_PARTY.md" rel="noopener noreferrer"&gt;THIRD_PARTY.md&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safety.&lt;/strong&gt; Etching chemicals burn skin and eyes. A diode laser can blind you and start fires, and paint, resist and etchant fumes are harmful: wear laser safety glasses rated for your module, ventilate, never leave a running laser alone. The laser G-code has &lt;strong&gt;never been run on a real machine&lt;/strong&gt;. Check every print against your real parts before etching.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What exists and what does not
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Place, wire, trace, trim and jumper tools, undo/redo, pan/zoom, flip view&lt;/td&gt;
&lt;td&gt;Works in tests and the screenshots; lightly tested by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right-click menus (trim, split, tidy, width, rotate, duplicate, wire from a pin, route one net...)&lt;/td&gt;
&lt;td&gt;Works in tests and the screenshots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rigid wires (Shift + Wire): a direct power trunk that the rest of the net branches off&lt;/td&gt;
&lt;td&gt;Works in tests and on the demo; first suggested from real use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-route retries (best of up to 6 net orders), jumpers optional globally or per net&lt;/td&gt;
&lt;td&gt;Works on the test boards; slower (up to about 12 s plus one try)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cut tool: one click removes a piece of trace from joint to joint (Shift: between two points)&lt;/td&gt;
&lt;td&gt;Works in tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Favourites &amp;amp; recently placed parts; Shift + click turns a part while placing&lt;/td&gt;
&lt;td&gt;Works in tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branches stay joined when a trunk is widened or tidied; DRC warns about hanging trace ends&lt;/td&gt;
&lt;td&gt;Works in tests and on rebuilds of real boards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Board &amp;gt; Keep traces out from under parts&lt;/td&gt;
&lt;td&gt;Works on the test boards; can add jumpers on cramped boards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pin labels (B C E, G D S, K A, IN GND OUT, optocoupler and DIP layouts) and part values on the board&lt;/td&gt;
&lt;td&gt;Works; layouts are the common ones for each shape, &lt;strong&gt;check your datasheet&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragging a trace section sideways (neighbours follow, 45° rules kept)&lt;/td&gt;
&lt;td&gt;Works in tests; lightly tested by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90 through-hole parts in a form-factor tree with a live preview, footprint wizard, KiCad &lt;code&gt;.kicad_mod&lt;/code&gt; import&lt;/td&gt;
&lt;td&gt;Works; some parts excluded on purpose (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trace width slider 0.4–10 mm (segment, trace or net) + current readout + "fits up to" hint&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide traces leave pads straight and end on the pad centre (auto-tidy)&lt;/td&gt;
&lt;td&gt;Works on the test boards; may leave a trace as it was when the tidy shape does not fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deadbug (surface-solder, no-drill) pads, per part or per pad&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chamfered corners everywhere + DRC&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-place and auto-route with jumper fallback&lt;/td&gt;
&lt;td&gt;Works on the three demo boards and random test boards; &lt;strong&gt;pure Python, can be slow&lt;/strong&gt; on big boards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gerber + drill zip, 1:1 PDF/SVG, BOM CSV, netlist text&lt;/td&gt;
&lt;td&gt;Written and re-read by an independent Gerber library in tests; &lt;strong&gt;not opened in a fab's own viewer&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laser G-code (negative / isolation / positive, test cards, frame file)&lt;/td&gt;
&lt;td&gt;Geometry checked in tests; &lt;strong&gt;never run on a real Elegoo Phecda&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows and macOS launchers, GitHub workflows&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not run&lt;/strong&gt; (written but untested on those systems)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Floating slider pop-up beside the cursor&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not built&lt;/strong&gt;: the slider is in the right-hand panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full PathFinder-style negotiated routing, topological router, C++ speed-up&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not built&lt;/strong&gt; (see &lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/DESIGN_NOTES.md" rel="noopener noreferrer"&gt;DESIGN_NOTES.md&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Soldermask and copper-pour layers, multi-layer boards, SMD&lt;/td&gt;
&lt;td&gt;Out of scope&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Quick tour: your first board in 5 minutes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;File &amp;gt; Open example &amp;gt; 555 LED flasher&lt;/strong&gt; (or &lt;strong&gt;File &amp;gt; New board&lt;/strong&gt; and set a size).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Place parts:&lt;/strong&gt; pick from &lt;em&gt;Favourites &amp;amp; recent&lt;/em&gt; at the top of the parts panel, or open a group in the parts panel on the left (the little arrows show the sub-types, such as &lt;em&gt;Resistors standing&lt;/em&gt;), pick a part, check the drawing under the list, then click on the board. Or type in the search box (&lt;code&gt;dip 8&lt;/code&gt;, &lt;code&gt;to-220&lt;/code&gt;, &lt;code&gt;standing&lt;/code&gt;). &lt;code&gt;R&lt;/code&gt; or Shift + click turns the part, &lt;code&gt;Esc&lt;/code&gt; stops placing. Star parts you use a lot with the button under the preview.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wire:&lt;/strong&gt; press &lt;code&gt;W&lt;/code&gt;, click one pin, then another (or drag from pin to pin). A yellow rubber-band line shows the connection. Hold &lt;strong&gt;Shift&lt;/strong&gt; for a &lt;strong&gt;rigid wire&lt;/strong&gt; (see below). Click a rubber-band line to name its net or give it a class (Signal 0.8 mm, Power 2 mm, High current 5 mm; change them in Board &amp;gt; Board size and rules).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-place, then Auto-route&lt;/strong&gt; (toolbar). A progress bar with &lt;strong&gt;Cancel&lt;/strong&gt; shows while it works. Routes use 0°, 45° and 90° moves, and a 90° turn is always built as two 45° turns (a chamfer). If a trace can't get through, a &lt;strong&gt;wire-link jumper&lt;/strong&gt; (blue dashed) is added on the component side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Label:&lt;/strong&gt; select a part and type its &lt;em&gt;Value / type&lt;/em&gt; (NE555, BC547, 10k...), and pick its &lt;em&gt;Pin labels&lt;/em&gt; (B C E, G D S, IN GND OUT...).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tweak:&lt;/strong&gt; drag a trace section sideways to move it, use &lt;strong&gt;Trim&lt;/strong&gt; (&lt;code&gt;X&lt;/code&gt;) to cut a section out, and &lt;strong&gt;right-click&lt;/strong&gt; anything for its menu.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check&lt;/strong&gt;, then &lt;strong&gt;Export&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Feature by feature
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Library and parts.&lt;/strong&gt; 90 parts, sorted by shape (form factor) rather than by maker code. Each group has an arrow that opens its sub-types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Sub-types&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chips (DIP)&lt;/td&gt;
&lt;td&gt;Narrow (7.62 mm rows) DIP-4 to DIP-28, wide (15.24 mm rows) DIP-28 and DIP-40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transistors &amp;amp; regulators&lt;/td&gt;
&lt;td&gt;TO-92, TO-220 (2-pin, 3-pin, with heatsink outline), TO-247 / TO-3P&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resistors, diodes &amp;amp; inductors&lt;/td&gt;
&lt;td&gt;Resistors lying flat (1/4 W to 5 W), &lt;strong&gt;resistors standing (1/4 W at 2.54 and 5.08 mm, 1/2 W and 1 W at 5.08 mm)&lt;/strong&gt;, diodes lying flat and standing, axial and radial inductors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capacitors&lt;/td&gt;
&lt;td&gt;Ceramic disc, film / box, electrolytic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEDs&lt;/td&gt;
&lt;td&gt;3 mm and 5 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pots, switches &amp;amp; crystals&lt;/td&gt;
&lt;td&gt;Inline pots (2.54 and 5.08 mm), triangle trimmer, 6 mm tactile switch, HC-49 crystal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connectors&lt;/td&gt;
&lt;td&gt;Pin headers 1-row and 2-row, screw terminals, JST-XH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Board bits&lt;/td&gt;
&lt;td&gt;Wire links, mounting holes, solder and surface pads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;My parts&lt;/td&gt;
&lt;td&gt;Parts you made or imported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Click a part and a drawing of it (copper, holes, outline, pin numbers, pitch and drill) appears under the list, in the sidebar, with no pop-up. Names say what the part is ("Electrolytic cap, 5 mm pitch"), not an obscure model number. Left out on purpose because their sizes vary too much between makers: relay, USB-B, barrel jack, buzzer, fuse holder, panel pots with lugs, TO-126, bridge rectifiers. Make those with the wizard. Body outlines, the inductor, pot and trimmer layouts, and the standing-part pitches are generic: check them against your real part.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your own parts.&lt;/strong&gt; &lt;em&gt;Parts &amp;gt; New footprint...&lt;/em&gt; opens the wizard: type a pad table (name, x, y, pad size, drill), or click on the canvas to drop pads, or start from a DIP or header preset, then save. Saved parts live in &lt;code&gt;~/.solotrace/parts&lt;/code&gt; (&lt;em&gt;Parts &amp;gt; Open my parts folder&lt;/em&gt;). &lt;em&gt;Parts &amp;gt; Import KiCad .kicad_mod...&lt;/em&gt; reads the text format of a KiCad footprint (through-hole pads and silkscreen lines only).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trace width.&lt;/strong&gt; Press &lt;code&gt;T&lt;/code&gt; and click along the board to draw a trace (&lt;code&gt;Enter&lt;/code&gt;, a double-click or &lt;em&gt;Finish trace here&lt;/em&gt; on the right-click menu finishes it; corners are chamfered for you). To change a width, click a &lt;strong&gt;trace section&lt;/strong&gt; on the drawing, or click a &lt;strong&gt;yellow rubber-band line&lt;/strong&gt; to set the width of a whole net, then drag the &lt;strong&gt;slider&lt;/strong&gt; in the right panel (0.4–10 mm; preset buttons 0.4, 0.8, 1.5, 2.5, 5, 10). Choose whether it applies to the section, the whole trace or the whole net. The panel shows roughly how many amps that width carries (IPC-2221 outer-layer formula; board settings give copper weight and temperature rise; a guide only) and how wide the trace can go where it is before it breaks the clearance rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rigid wires (power trunks).&lt;/strong&gt; A net with more than two pins is normally routed by joining the nearest pins first, so a ground net can end up as one long chain (transistor to capacitor to terminal). If you want a direct trace between two particular pins, hold &lt;strong&gt;Shift&lt;/strong&gt; while wiring them (click or drag), or right-click a pin &amp;gt; &lt;em&gt;Rigid wire from this pin&lt;/em&gt;, or right-click an existing rubber band &amp;gt; &lt;em&gt;Make ... rigid&lt;/em&gt;. The router routes every rigid pair first, as its own pin-to-pin trace, then connects the rest of the net to the nearest point of that trunk, so a capacitor becomes a short branch. "Rigid" fixes &lt;em&gt;which&lt;/em&gt; two pins are joined, not the path: the trunk bends round other parts like any trace, can be moved by a re-route, and you can drag or cut it. Rigid rubber bands are thicker and orange; the routed trunk is drawn in orange copper (it is ordinary copper in every export). The net panel lists the rigid pairs, each with a &lt;em&gt;make normal&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Values and pin labels.&lt;/strong&gt; Select a part: &lt;em&gt;Value / type&lt;/em&gt; at the top of the right panel takes anything (NE555, BC547, IRF540, 7805, 10k, 100uF 25V) and is shown next to the part name on the board; double-click a part to jump to it. &lt;em&gt;Pin labels&lt;/em&gt; offers the common layouts for that shape, not specific part numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;Layouts offered&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TO-92&lt;/td&gt;
&lt;td&gt;E B C, C B E, B C E, D G S, G D S, S G D, small regulator OUT GND IN / IN GND OUT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TO-220 3-pin&lt;/td&gt;
&lt;td&gt;power transistor B C E, MOSFET G D S, IGBT G C E, regulator IN GND OUT, adjustable regulator ADJ OUT IN, negative regulator GND IN OUT, triac MT1 MT2 G, dual diode A K A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TO-220 2-pin&lt;/td&gt;
&lt;td&gt;diode K A / A K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TO-247&lt;/td&gt;
&lt;td&gt;G D S, G C E, B C E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DIP-4 / DIP-6&lt;/td&gt;
&lt;td&gt;optocoupler A K E C; optocoupler with base pin; opto-triac&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DIP-8&lt;/td&gt;
&lt;td&gt;555-style timer, dual op-amp, single op-amp, dual optocoupler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DIP-14 / 16 / 20&lt;/td&gt;
&lt;td&gt;logic GND and VCC pins; quad op-amp (DIP-14)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pots, connectors&lt;/td&gt;
&lt;td&gt;1 W 3, CCW W CW; + -, - +, AC AC, IN OUT, V+ GND V-, IN GND OUT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Or type your own in pin order, separated by spaces (&lt;code&gt;.&lt;/code&gt; skips a pin). Diodes and LEDs show K A, electrolytics + -, without asking. Pin orders differ between makers, so &lt;strong&gt;always check the datasheet&lt;/strong&gt; of the part you have. Labels are shown in the editor; they are not yet written into the Gerber silkscreen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wide traces are tidied.&lt;/strong&gt; When a trace is 1.2 mm or wider, SoloTrace makes it leave each pad with a straight vertical or horizontal run (about the pad radius plus the trace width) before it turns, ends it exactly on the pad centre, and makes the chamfers bigger where there is room. This happens after auto-routing and, with &lt;em&gt;Auto-tidy when the width changes&lt;/em&gt; ticked (the default), whenever you change a width. If the tidier shape would touch other copper the trace is left as it was, and the clearance problem is marked in red. &lt;em&gt;Tidy&lt;/em&gt; (right panel or right-click) does it on demand; &lt;em&gt;Edit &amp;gt; Tidy all traces&lt;/em&gt; does the whole board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cutting a piece out of a trace.&lt;/strong&gt; Press &lt;code&gt;C&lt;/code&gt; and click a trace: the piece under the mouse goes, from the joint before it to the joint after it (a pad, a place where another trace meets it, a jumper pad). The rest of the trace and of the net stays. While you hover, the piece that would go is shown in red. Right-clicking a trace only opens its menu; rest the mouse on &lt;em&gt;Cut this piece&lt;/em&gt; to see the piece in red, and choose it to remove it. To cut between two points of your own choosing, Shift + click the first point and Shift + click the second (or right-click &amp;gt; &lt;em&gt;Cut between two points&lt;/em&gt;); &lt;code&gt;Esc&lt;/code&gt; cancels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moving, trimming and splitting traces.&lt;/strong&gt; With &lt;em&gt;Select&lt;/em&gt;, drag a trace section sideways: it moves parallel to itself and the sections either side stretch to follow at their own angle, so no 90° corner appears. Ends on pads stay put (a 45° link is added). &lt;strong&gt;Trim&lt;/strong&gt; (&lt;code&gt;X&lt;/code&gt;, or right-click &amp;gt; &lt;em&gt;Trim this section&lt;/em&gt;) cuts one section out and leaves the rest as separate pieces; &lt;em&gt;Split trace here&lt;/em&gt; breaks a trace in two without removing copper. A trace you have moved or trimmed counts as hand-drawn, so Auto-route keeps it. &lt;code&gt;Ctrl+Z&lt;/code&gt; undoes any of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right-click menus.&lt;/strong&gt; Right-click (without dragging) on a trace, pin, part, rubber-band line, jumper or empty board for the actions that fit: widths, trim, split, tidy, highlight or re-route the net, rotate either way, duplicate, lock, pad styles, wire or trace from a pin, disconnect, delete, and Undo/Redo. Right-drag still pans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small helpers.&lt;/strong&gt; Hover over copper to light up its whole net (the net name shows in the corner). Arrow keys nudge the selected part by 1.27 mm (&lt;code&gt;Shift&lt;/code&gt;: 2.54 mm), &lt;code&gt;Ctrl+D&lt;/code&gt; duplicates it, &lt;code&gt;Shift+R&lt;/code&gt; turns it back. &lt;em&gt;File &amp;gt; Open recent&lt;/em&gt; lists your last boards. While a board has unsaved changes it is saved every 2 minutes to &lt;code&gt;~/.solotrace/autosave.solotrace&lt;/code&gt;; &lt;em&gt;File &amp;gt; Recover autosave&lt;/em&gt; opens it (the file is removed when you quit normally). Hover over a toolbar button for a hint. The status bar shows the cursor position in mm and a green &lt;em&gt;all routed&lt;/em&gt; or orange &lt;em&gt;N to route&lt;/em&gt; badge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pads: through-hole or deadbug.&lt;/strong&gt; Pick &lt;em&gt;Through-hole&lt;/em&gt;, &lt;em&gt;Deadbug&lt;/em&gt; or &lt;em&gt;Both&lt;/em&gt; in the toolbar for new parts, or change it for a placed part in the right panel, or for one pad ("This pad only"). Deadbug pads have &lt;strong&gt;no hole&lt;/strong&gt; and are drawn as a long oval that sticks out from the part so you can bend the lead flat and solder it on the copper side. "Both" keeps the hole &lt;em&gt;and&lt;/em&gt; the extra copper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-place.&lt;/strong&gt; Simulated annealing that moves, rotates and swaps unlocked parts to shorten the ratsnest and cut crossings. Tick &lt;strong&gt;Lock&lt;/strong&gt; on a part to keep it where it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-route.&lt;/strong&gt; A grid router that remembers its heading, so it prefers straight runs and clean 45° bends instead of staircases. Wide nets are routed first, and it prefers not to run under part bodies. If a net is blocked it tears up the nets in the way and retries; as a last resort it adds a jumper, unless you unticked &lt;strong&gt;Jumpers&lt;/strong&gt; in the tool bar or for that net. The whole job is tried with up to six different net orders (about 12 seconds at most, plus the try in progress) and the best result is kept; it stops early when a try routes everything without jumpers. Traces you drew by hand are kept. &lt;em&gt;Board &amp;gt; Remove automatic routes&lt;/em&gt; undoes only the automatic ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jumpers.&lt;/strong&gt; Press &lt;code&gt;J&lt;/code&gt; to add one by hand. They are plain wire links on the component side, in 2.54 mm steps from 5.08 mm. They are listed in the DRC window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;View.&lt;/strong&gt; &lt;code&gt;M&lt;/code&gt; flips between the &lt;strong&gt;component side&lt;/strong&gt; (how you place parts) and the &lt;strong&gt;copper side&lt;/strong&gt; (how the etched board looks from below). Wheel zooms, right or middle drag pans, right-click opens the menu, &lt;code&gt;F&lt;/code&gt; fits the board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DRC.&lt;/strong&gt; &lt;em&gt;Check&lt;/em&gt; lists trace ends that are not joined to anything ("dangling"), copper too close to other copper or the board edge, parts sticking out of the board, traces thinner than the minimum, turns sharper than 45°, drilled pads with too little copper ring, jumpers that are the wrong length or cross each other, and pins that are not connected yet. Click a row to jump to it on the board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exports (Export menu).&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gerber + drill (zip)&lt;/td&gt;
&lt;td&gt;Copper (bottom), board edge, silkscreen, Excellon drill. Single layer; &lt;strong&gt;no soldermask layer&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:1 PDF / SVG, mirrored&lt;/td&gt;
&lt;td&gt;Toner transfer or photo-resist film: the copper side as you will see it. Always print at 100 % and &lt;strong&gt;measure a known distance&lt;/strong&gt; before etching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:1 PDF, design view&lt;/td&gt;
&lt;td&gt;Component-side print&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bill of materials (CSV)&lt;/td&gt;
&lt;td&gt;Parts grouped by name and value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Netlist text&lt;/td&gt;
&lt;td&gt;Round-trip text format for a quick board from a text editor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laser G-code (Elegoo Phecda)&lt;/td&gt;
&lt;td&gt;See below&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Laser etching with the Elegoo Phecda (gantry diode laser)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Read this first.&lt;/strong&gt; A diode laser cannot remove bare copper. The method is: &lt;strong&gt;paint&lt;/strong&gt; the copper, let the laser &lt;strong&gt;burn the paint away&lt;/strong&gt; where copper must go, &lt;strong&gt;etch&lt;/strong&gt;, then clean the paint off. Nobody has run these files on a Phecda yet. Treat every default as a starting guess and use the test cards.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finish the board and run DRC. Open &lt;strong&gt;Export &amp;gt; Laser G-code (Elegoo Phecda)...&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mode&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Negative&lt;/strong&gt; (default): burns everything that is &lt;em&gt;not&lt;/em&gt; copper, so paint is left only on the traces and pads. Slowest, cleanest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolation&lt;/strong&gt;: burns only a band of &lt;code&gt;gap&lt;/code&gt; mm around the copper. Fast; leftover paint between bands must be cleared by hand or by the etchant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Positive&lt;/strong&gt;: burns the copper shapes themselves (for a negative-working resist or marking).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjust&lt;/strong&gt; (all live-previewed):

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Trace adjust&lt;/em&gt; (mm per side): positive grows the copper, negative thins it and widens every gap between traces. Use this to correct for the laser's spot size and your etch.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Gap&lt;/em&gt; (isolation band width) and &lt;em&gt;Border&lt;/em&gt; (keep paint at the board edge).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Line interval&lt;/em&gt; (default 0.08 mm; the module's spot is about 0.06–0.13 mm depending on model), &lt;em&gt;feed&lt;/em&gt; (default 1500 mm/min), &lt;em&gt;power&lt;/em&gt; (default 70 % of S max), &lt;em&gt;passes&lt;/em&gt;, &lt;em&gt;crosshatch&lt;/em&gt; (second pass at 90°), &lt;em&gt;bidirectional&lt;/em&gt; scanning, &lt;em&gt;M4 dynamic power&lt;/em&gt; (default) or &lt;em&gt;M3 with overscan&lt;/em&gt;, and &lt;em&gt;drill dots&lt;/em&gt; (a tiny burn at every hole centre as a drilling guide).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save G-code...&lt;/strong&gt;. Also use &lt;strong&gt;Frame file...&lt;/strong&gt; (traces the board outline at 1 % power so you can line up the paint) and, on first use, &lt;strong&gt;Speed/power test...&lt;/strong&gt; and &lt;strong&gt;Trace/gap test...&lt;/strong&gt; (a grid of squares and a ladder of fine traces and gaps).&lt;/li&gt;
&lt;li&gt;In LaserGRBL or LightBurn: jog the laser to the &lt;strong&gt;lower-left corner of the board as seen from the copper side&lt;/strong&gt;, set work zero there, open the file, check the preview, run the frame, then burn. The exporter mirrors X for you because the design view is the component side.&lt;/li&gt;
&lt;li&gt;Controller assumptions: GRBL-style, &lt;code&gt;$30&lt;/code&gt; equal to the S max in the dialog (default 1000) and &lt;code&gt;$32=1&lt;/code&gt; (laser mode). The Elegoo manual names LaserGRBL and LightBurn but does not say "GRBL"; see &lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/docs/research/phecda-laser-notes.md" rel="noopener noreferrer"&gt;docs/research/phecda-laser-notes.md&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Examples to look at without installing anything: &lt;a href="https://github.com/The-Dorkknight/solotrace/tree/main/examples" rel="noopener noreferrer"&gt;examples/&lt;/a&gt; has four boards, each with its &lt;code&gt;.solotrace&lt;/code&gt; file, Gerber zip, 1:1 copper-side SVG and PDF, BOM and laser G-code in both modes, plus &lt;a href="https://github.com/The-Dorkknight/solotrace/tree/main/examples/test-cards" rel="noopener noreferrer"&gt;test cards&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/HOW_IT_WORKS.md" rel="noopener noreferrer"&gt;HOW_IT_WORKS.md&lt;/a&gt;: what it reads and writes, the coordinates, the router and the laser engine in plain English&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/DESIGN_NOTES.md" rel="noopener noreferrer"&gt;DESIGN_NOTES.md&lt;/a&gt;: decisions, rejected ideas, measurements, what is untested&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/solotrace/tree/main/docs/research" rel="noopener noreferrer"&gt;docs/research/&lt;/a&gt;: the original research and plan, and the Phecda notes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/solotrace/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Project page (after you enable GitHub Pages): &lt;code&gt;https://The-Dorkknight.github.io/solotrace/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F107f47jjtmzhmnuxhfqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F107f47jjtmzhmnuxhfqo.png" alt="apple touch icon" width="180" height="180"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/solotrace" rel="noopener noreferrer"&gt;The-Dorkknight/solotrace on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A POINT of contention</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Tue, 20 Oct 2026 14:43:06 +0000</pubDate>
      <link>https://dev.to/open-madscience/a-point-of-contention-3o03</link>
      <guid>https://dev.to/open-madscience/a-point-of-contention-3o03</guid>
      <description>&lt;p&gt;&lt;strong&gt;STL to STEP conversion shouldn't be that HARD: a proposed hybrid STL format that is CAD-friendly and slicer-compatible&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Converting an STL back to a STEP file means guessing the original CAD faces. This paper proposes a normal binary STL that also records them, so slicers still open it and converters no longer have to guess. It is also a public, dated description of the format, and a list of the ideas that didn't work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Matthew Armstrong · GitHub &lt;a href="https://github.com/The-Dorkknight" rel="noopener noreferrer"&gt;The-Dorkknight&lt;/a&gt; · United Kingdom\&lt;br&gt;
First published 15 October 2026 · Version 1.0\&lt;br&gt;
Text and figures: &lt;a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="noopener noreferrer"&gt;CC BY-SA 4.0&lt;/a&gt;. Code that comes with it: &lt;a href="https://polyformproject.org/licenses/noncommercial/1.0.0/" rel="noopener noreferrer"&gt;PolyForm Noncommercial 1.0.0&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/blob/main/paper/a-point-of-contention-v1.0.pdf" rel="noopener noreferrer"&gt;PDF&lt;/a&gt;&lt;/strong&gt; · 🌐 &lt;a href="https://The-Dorkknight.github.io/a-point-of-contention/" rel="noopener noreferrer"&gt;Web page&lt;/a&gt; · 🔧 &lt;a href="https://github.com/The-Dorkknight/hardpoint-stl" rel="noopener noreferrer"&gt;The FreeCAD and Fusion plug-ins&lt;/a&gt; · About this repository&lt;/p&gt;

&lt;p&gt;This page is the full paper (the same text and figures as the PDF).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contents:&lt;/strong&gt; Abstract · Why I wrote this · The problem: what STL loses, and how slicers read it · Prior art: what I found (3–10 October 2026) · How the idea was judged · The ideas that didn't work · The format, in one picture · Does it help? · What works, and what doesn't yet · Defensive disclosure: the hardpoint STL and variations · What I'd test next (feel free to use this plan) · Working together · How this was made · Code, data and how to reproduce · Licence, and keeping it free · Disclaimers · How to cite · References · Appendix A: a real hardpoint STL&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Status: nothing in this paper has been run in real FreeCAD, in Fusion, or in a slicer.&lt;/strong&gt; The format and its reference code were tested on Linux, using test parts built with OpenCASCADE and the file-reading code that slicers use (not the slicers themselves). The FreeCAD and Fusion exporters were only tested against stand-ins for those programs. The conversion results come from one converter of mine. If you open the example files in your slicer before I do, please tell me what happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not legal advice.&lt;/strong&gt; I am not a lawyer. Where this paper talks about prior art, it says what I found, where I found it, and when. A patent attorney is the person to ask about what it means.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plain STL: 1,103 faces&lt;/th&gt;
&lt;th&gt;Hardpoint STL: 8 faces&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo8yjh82mrtjp57v985e1.webp" alt="The knob converted from a plain STL: many flat strips and a dark faceted area" width="800" height="610"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F455d0jrnqihjla5ijo06.webp" alt="The knob converted from its hardpoint twin: one cylinder, one sphere, a smooth rounded edge" width="800" height="610"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The same knob, made from the same triangles, converted to STEP. Left: from a plain STL, which came out with 1,103 faces. Right: from the hardpoint version of the file, which came out with 8 faces. The original CAD model has 4.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;An STL file is just a pile of triangles. It has lost everything the CAD program knew: which triangles made up one flat face, which made up a round hole, and where the corners were. Turning an STL back into a real CAD solid means guessing all of that again, and the guesses go wrong most often on coarse meshes and curved edges.&lt;/p&gt;

&lt;p&gt;This paper describes a way to keep that information in the file without breaking it for anyone else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The idea:&lt;/strong&gt; write the CAD facts into the two places a binary STL already has spare room that slicers ignore: the 80-byte header, and the 2-byte "attribute" field after every triangle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What goes in:&lt;/strong&gt; which face each triangle belongs to (as a small colour number, like colouring a map so that neighbouring areas differ), what kind of surface the face is, and which triangle corners are CAD vertices ("hardpoints"). A checksum in the header shows whether the mesh was edited afterwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What a slicer sees:&lt;/strong&gt; the same triangles, the same file size, the same &lt;code&gt;.stl&lt;/code&gt; extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How much it helps:&lt;/strong&gt; in my tests, a converter given the labelled file turned a knob into a STEP solid with 8 faces. From the same triangles without labels it made 1,103 faces (the original CAD model has 4). On a clean, fine bracket the gain was small: 21 faces down to 17, against 16 in the original.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What didn't work:&lt;/strong&gt; data after the last triangle, a 9-bit face number, hiding the whole STEP file inside the STL, remembering how freeform faces were cut up, and a handful of mistakes along the way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Section 8 describes the format and its variations in enough detail to build them. It is written as prior art, so the method stays free for everyone to use.&lt;/p&gt;




&lt;h2&gt;
  
  
  0. Why I wrote this
&lt;/h2&gt;

&lt;p&gt;How it started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I convert STL files into STEP files. Most of the converter's effort goes on working out again what the CAD program already knew.&lt;/li&gt;
&lt;li&gt;The CAD program could say it at the moment it writes the STL. Instead it throws the answer away.&lt;/li&gt;
&lt;li&gt;A binary STL has two spare places that slicers ignore. I'm a hobbyist in the UK with a laptop, and I wanted to see if the answer could be kept there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I built the &lt;strong&gt;hardpoint STL&lt;/strong&gt;, plus an exporter for FreeCAD and one for Fusion. First I researched how slicers read STL files. Then I tried to find every way a file like this could break them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why publish all of this, including the failures?&lt;/strong&gt; Four reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Protection.&lt;/strong&gt; I looked, and found no earlier description of putting CAD face labels inside an STL (section 2). If that is right, a dated, detailed public description is a cheap way to keep the idea free for hobbyists. That is what a &lt;em&gt;defensive publication&lt;/em&gt; is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saving you time.&lt;/strong&gt; Every dead end here is one you don't have to walk down. The best one began as a question of mine that turned out to have a simple numerical answer (section 4.3).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getting other people involved.&lt;/strong&gt; The format has room for more exporters (CadQuery, Onshape, SolidWorks) and more readers than I can write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selfishly:&lt;/strong&gt; by my calculations, a third paper makes me about 0.7 % more employable. The first was worth 22.7 % and the second 3.14 %, so the returns are falling. That's probably fine. Publish too many and I start to look like I have a lot of free time, and nobody wants to hire someone who looks that available.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;How this was made.&lt;/strong&gt; The ideas, the questions and the rules are mine. The code, the tests, the measurements and the first draft of this write-up were done with an AI assistant (Claude, by Anthropic), working to my design. All sources are linked so you can check them, and the code is included so you can re-run every number that can be re-run (section 12).&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The problem: what STL loses, and how slicers read it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 What gets lost
&lt;/h3&gt;

&lt;p&gt;A CAD solid is made of faces: planes, cylinders, spheres, cones, tori and freeform surfaces, joined along edges and corners. Exporting it as STL cuts every face into triangles and writes only the triangles. A flat face and a rounded edge of the same bracket both end up as nothing but triangles.&lt;/p&gt;

&lt;p&gt;A mesh-to-CAD converter has to work out again which triangles form one flat face (and how flat is flat enough?) and which form a curve. When the mesh is coarse, or a curve meets a flat face very gently, it can't always tell.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 What I set myself
&lt;/h3&gt;

&lt;p&gt;The design had to meet six requirements, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recognised as an STL.&lt;/strong&gt; The extension stays &lt;code&gt;.stl&lt;/code&gt; and the file loads in any slicer without a plug-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identical to a normal STL in a slicer.&lt;/strong&gt; Same triangles, same file size, nothing a slicer acts on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Makes STL-to-STEP conversion easier.&lt;/strong&gt; It carries what a converter otherwise guesses: faces, surface types, corners.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writable from inside CAD programs.&lt;/strong&gt; FreeCAD's and Fusion's own Python can't be assumed to have NumPy, so the writer uses the standard library only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never makes a conversion worse.&lt;/strong&gt; Labels are hints. The reader checks each one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs on low-end hardware.&lt;/strong&gt; The target machine is a 7th-generation Core i3.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1.3 What STL readers do with a file
&lt;/h3&gt;

&lt;p&gt;A binary STL has an 80-byte header, a 4-byte triangle count, then 50 bytes per triangle: 12 for the normal, 36 for three corners, and 2 for an "attribute byte count" that slicers ignore. So there are exactly two places that carry no meaning to a slicer. Anything after the last triangle is a different matter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reader&lt;/th&gt;
&lt;th&gt;Used by&lt;/th&gt;
&lt;th&gt;Binary or text test&lt;/th&gt;
&lt;th&gt;Triangle count&lt;/th&gt;
&lt;th&gt;Extra bytes at the end&lt;/th&gt;
&lt;th&gt;How this is known&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;admesh &lt;code&gt;stlinit.cpp&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;OrcaSlicer; PrusaSlicer-family slicers share this code&lt;/td&gt;
&lt;td&gt;Binary if any of the 128 bytes after offset 84 is above 127&lt;/td&gt;
&lt;td&gt;Taken from the file size; the header count only logs a warning&lt;/td&gt;
&lt;td&gt;Rejected unless a multiple of 50, then read as phantom triangles&lt;/td&gt;
&lt;td&gt;Read &lt;a href="https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer/main/deps_src/admesh/stlinit.cpp" rel="noopener noreferrer"&gt;OrcaSlicer's source&lt;/a&gt; (PrusaSlicer's and Bambu Studio's copies not read)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;numpy-stl&lt;/td&gt;
&lt;td&gt;Cura, through &lt;a href="https://raw.githubusercontent.com/Ultimaker/Uranium/main/plugins/FileHandlers/STLReader/STLReader.py" rel="noopener noreferrer"&gt;Uranium's STLReader&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Automatic mode&lt;/td&gt;
&lt;td&gt;From the header&lt;/td&gt;
&lt;td&gt;Load fails with an error&lt;/td&gt;
&lt;td&gt;Read Uranium's source; ran the library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;trimesh&lt;/td&gt;
&lt;td&gt;many Python tools&lt;/td&gt;
&lt;td&gt;Binary only if the size is exactly 84 + 50 × count, otherwise tried as text&lt;/td&gt;
&lt;td&gt;From the header&lt;/td&gt;
&lt;td&gt;Not binary, then fails as text (0 faces)&lt;/td&gt;
&lt;td&gt;Read &lt;a href="https://raw.githubusercontent.com/mikedh/trimesh/main/trimesh/exchange/stl.py" rel="noopener noreferrer"&gt;trimesh's source&lt;/a&gt;; ran the library&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Other findings that shaped the format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The admesh text test misreads simple files.&lt;/strong&gt; A cube at whole-millimetre coordinates with zero normals has no byte above 127 in that window, so it is read as text and imports empty. This is &lt;a href="https://github.com/OrcaSlicer/OrcaSlicer/issues/15993" rel="noopener noreferrer"&gt;OrcaSlicer issue 15993&lt;/a&gt;, open when I checked on 3 October 2026. A probe copying the test's logic reproduced it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The header must not start with &lt;code&gt;solid&lt;/code&gt;.&lt;/strong&gt; Some software then treats the file as text (&lt;a href="https://en.wikipedia.org/wiki/STL_(file_format)" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two colour conventions already use the attribute field.&lt;/strong&gt; VisCAM and SolidView: bits 0–4 blue, 5–9 green, 10–14 red, bit 15 = 1 means the colour is valid. Materialise Magics: a &lt;code&gt;COLOR=&lt;/code&gt; string in the header, bits 0–4 red, 5–9 green, 10–14 blue, bit 15 = 0 means the triangle has its own colour (both as described on Wikipedia).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;trimesh drops the attribute field during clean-up.&lt;/strong&gt; With &lt;code&gt;process=True&lt;/code&gt; it survives vertex merging, but removing sliver triangles rebuilds the mesh. On one test part 5,572 triangles became 5,564 and the field was gone.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Prior art: what I found (3–10 October 2026)
&lt;/h2&gt;

&lt;p&gt;Using the attribute bytes of an STL for something other than "nothing" is old: the two colour conventions above are the best-known uses. What I looked for was a file that keeps &lt;strong&gt;CAD face identity, surface types and CAD vertices&lt;/strong&gt; inside an ordinary STL.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I found no description of that.&lt;/strong&gt; The searches were ordinary web and code searches by me and my AI assistant, over a few days. They are not a professional prior-art search, and a miss here proves little.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing mesh-to-STEP tools&lt;/strong&gt; (&lt;a href="https://github.com/Crypto69/stlToSolid" rel="noopener noreferrer"&gt;stlToSolid&lt;/a&gt;, &lt;a href="https://pypi.org/project/breptile/0.1.0/" rel="noopener noreferrer"&gt;breptile&lt;/a&gt;) segment the mesh and fit primitives from the triangles alone. As far as I could tell neither reads hints from the file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The standard container for "a mesh plus the real CAD in one file"&lt;/strong&gt; is 3MF, which is a zip. It is not an STL, and I did not test how slicers treat an extra file inside one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether any of this affects anyone's rights is a question for lawyers, not me.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. How the idea was judged
&lt;/h2&gt;

&lt;p&gt;I can't build exact CAD parts by hand, so I had a CAD kernel make them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test parts.&lt;/strong&gt; Two parts built with OpenCASCADE (the kernel FreeCAD uses): a bracket (rounded corners, three holes, a boss, a countersink) and a knob (cylinder, sphere, torus fillet). The bracket was meshed at two settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twin files.&lt;/strong&gt; Each mesh was written twice with identical triangles: once labelled, once as a plain STL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The converter.&lt;/strong&gt; Both files went through STL2STEP, an earlier converter of mine (since superseded; its code is not in this repository), default settings. I counted the faces in the STEP result, the largest distance from the mesh, and the volume against the original CAD solid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The format itself.&lt;/strong&gt; Round trips (write, read back, compare faces, types and hardpoints), tamper checks, and the loaders of section 1.3.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What this gets wrong&lt;/strong&gt; (please read before quoting a number):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three test parts, built for the tests. No real downloaded models.&lt;/li&gt;
&lt;li&gt;One converter. Another converter may use the labels better or worse.&lt;/li&gt;
&lt;li&gt;Linux only. The writer was timed on a cloud machine, not on a Core i3.&lt;/li&gt;
&lt;li&gt;The FreeCAD and Fusion code ran against stand-ins with the same attribute names, filled with OpenCASCADE geometry. That checks the plug-ins' own logic and nothing about how the real programs behave.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. The ideas that didn't work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 The table
&lt;/h3&gt;

&lt;p&gt;Seven ideas were built or tested and then dropped. Each row says what the evidence was, so nobody repeats the experiment blind.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Idea&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Store extra data after the last triangle&lt;/td&gt;
&lt;td&gt;Loaders fail or read garbage&lt;/td&gt;
&lt;td&gt;numpy-stl raised an error, trimesh loaded 0 faces, admesh's logic reads phantom triangles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A global face number in 9 bits&lt;/td&gt;
&lt;td&gt;Replaced by colours&lt;/td&gt;
&lt;td&gt;Capped at 511 faces; colours need 2 to 5 values for any count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hide the STEP file itself in the STL&lt;/td&gt;
&lt;td&gt;It doesn't fit as text, and only sometimes when compressed&lt;/td&gt;
&lt;td&gt;Section 4.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remember how a freeform face was cut into patches (a second, 3-bit "patch colour")&lt;/td&gt;
&lt;td&gt;Faster but worse, so removed&lt;/td&gt;
&lt;td&gt;Organic part: 83 s down to 19 s, but 20 areas left as facets instead of 2 and maximum deviation 0.16 mm instead of 0.12 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Let the converter's normal detection handle cone and torus faces&lt;/td&gt;
&lt;td&gt;It chopped them into cylinder and sphere pieces&lt;/td&gt;
&lt;td&gt;Knob: 131 STEP faces and 3 failed faces, against 8 faces when treated as freeform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Label a whole-body mesh by nearest triangle centre&lt;/td&gt;
&lt;td&gt;Mislabelled triangles beside face borders&lt;/td&gt;
&lt;td&gt;About 1 % wrong on the knob, which turned into 11 false flats and 315 STEP faces. Point-to-triangle distance gave 100 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carry the attribute field through trimesh's clean-up&lt;/td&gt;
&lt;td&gt;Lost whenever slivers are removed&lt;/td&gt;
&lt;td&gt;5,572 triangles became 5,564 with no attribute data. Matching by position replaced it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A likely reason saved patch layouts gave a worse result (not proven): the fit of a patch depends on its border curves, and the converter smooths those borders step by step over several passes. Starting from the final layout skips the steps that made it fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Mistakes along the way
&lt;/h3&gt;

&lt;p&gt;These are in the order they happened. Most were the AI assistant's, and they were caught by tests I had asked for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A missing triangle.&lt;/strong&gt; The knob's sphere has a pole where one triangle collapses to a line once nearby points are welded. The writer rightly dropped it (1 of 2,959). The tests then compared labels with the wrong triangles, because the output order no longer matched the input. The encoder now reports which input triangle sits at each output position.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. About 1 % of triangles mislabelled.&lt;/strong&gt; Fusion's fallback labels a whole-body mesh from per-face meshes. First version: nearest triangle centre. It looked fine in a picture and mislabelled about 1 % of triangles beside face borders, which a converter read as 11 flat faces and 315 STEP faces. Point-to-triangle distance fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A flat disc shown as rough facets.&lt;/strong&gt; The converter's preview showed the top of the bracket's boss as dark left-as-facets. The rule that called a planar face a facet was "three edges or fewer"; a flat disc bounded by one circle qualifies. It now also requires every edge to be straight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A renamed OpenCASCADE method.&lt;/strong&gt; The test code called &lt;code&gt;TopoDS.Edge_s&lt;/code&gt;, which doesn't exist in the OCP 8 build installed. A small compatibility helper hides the difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. A changelog that claimed too much.&lt;/strong&gt; I compared my changelog with the public repository of the converter on the day I packaged it. Five bullets under the published version had been added after it was published. They moved to the new version's section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Screenshots taken at the wrong moment.&lt;/strong&gt; Two documentation screenshots showed a job queue card and a sandbox folder name. They were retaken in a clean state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. A plug-in never tried in its real program.&lt;/strong&gt; I could not install FreeCAD where this was built: its release downloads, conda-forge, the Ubuntu package source, the PPA and Flathub were all blocked, and I did not route around the block. So the workbench ran only against stand-ins. In its place, &lt;code&gt;tests/freecad_selfcheck.py&lt;/code&gt; runs inside the real program in a few seconds. It has not been run either. I keep repeating that because it is the most useful thing a reader could check.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Could the whole STEP file be hidden in the STL?
&lt;/h3&gt;

&lt;p&gt;Partway through I asked: would it be easier to hide a STEP file inside the STL as plain text? It was worth checking, because it would give the exact CAD model back with no fitting at all.&lt;/p&gt;

&lt;p&gt;It doesn't fit. The room in an STL grows with the triangle count (2 bytes each), while the size of a STEP file grows with the number of faces:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;STEP file&lt;/th&gt;
&lt;th&gt;STEP compressed&lt;/th&gt;
&lt;th&gt;Attribute room (2 bytes × triangles)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bracket, 1,180 triangles at 0.05 mm&lt;/td&gt;
&lt;td&gt;48.1 KB&lt;/td&gt;
&lt;td&gt;6.6 KB&lt;/td&gt;
&lt;td&gt;2.3 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knob, 2,958 triangles&lt;/td&gt;
&lt;td&gt;9.3 KB&lt;/td&gt;
&lt;td&gt;2.1 KB&lt;/td&gt;
&lt;td&gt;5.8 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Simple parts with big flat faces fit worst. A text (ASCII) STL has no comment syntax; from reading OrcaSlicer's reader, extra text lines inflate its triangle count and the load fails (read, not run). A compressed STEP would also need every attribute bit, including bit 15, and would compete with the labels for the same bytes. It could be an optional extra: store it when it fits, fall back to labels when it doesn't. That is a variation in section 8.6, not a result.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The format, in one picture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpmdqv1iidltl3lj8y83l.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpmdqv1iidltl3lj8y83l.webp" alt="Byte layout of a binary STL with the header and the 2 attribute bytes of each triangle highlighted, and the meaning of each of the 16 attribute bits" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drawn from the specification by &lt;code&gt;tools/make_illustrations.py&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Header&lt;/strong&gt; (80 bytes of ASCII, padded with spaces):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HARDPOINT-STL 1 u=mm tol=0.05 ang=15 crc=e67609a0 by=cad-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HARDPOINT-STL 1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Marker and format version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;u&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Units: &lt;code&gt;mm&lt;/code&gt;, &lt;code&gt;in&lt;/code&gt;, &lt;code&gt;cm&lt;/code&gt; or &lt;code&gt;m&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tol&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How far triangles may be from the true surface, in those units; 0 = unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ang&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Meshing or sharp-edge angle in degrees; 0 = unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CRC-32 of the 48 geometry bytes (normal and corners) of every triangle, in file order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;by&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What wrote the file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Attribute field&lt;/strong&gt; (16 bits per triangle, little-endian):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bits&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0–4&lt;/td&gt;
&lt;td&gt;Face colour, 0 to 31. Faces that touch along an edge never share a colour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5–6&lt;/td&gt;
&lt;td&gt;Reserved, 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1 = this triangle is labelled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8–10&lt;/td&gt;
&lt;td&gt;Surface type: 0 unknown, 1 plane, 2 cylinder, 3 sphere, 4 cone, 5 torus, 6 freeform, 7 keep as facets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11–13&lt;/td&gt;
&lt;td&gt;Hardpoint flags for corner 1, 2 and 3 of the triangle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Reserved, 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Always 0 (some viewers read bit 15 = 1 as "this field is a colour")&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4fgdev4ca0v181gb62m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4fgdev4ca0v181gb62m.webp" alt="One hardpoint STL drawn three ways: the mesh a slicer sees, the face colours stored in the file, and the surface types with hardpoints" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The bracket example (&lt;code&gt;examples/bracket_hardpoint.stl&lt;/code&gt;): 1,180 triangles, 16 faces labelled with 3 colours, 25 hardpoints. Drawn from the file itself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Why these choices (every one follows from section 1.3 or from a measurement):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Chosen&lt;/th&gt;
&lt;th&gt;Rejected&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where the labels live&lt;/td&gt;
&lt;td&gt;Header and attribute fields&lt;/td&gt;
&lt;td&gt;Data after the last triangle&lt;/td&gt;
&lt;td&gt;A trailer fails in numpy-stl and trimesh and becomes phantom triangles in admesh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How a face is identified&lt;/td&gt;
&lt;td&gt;A 5-bit colour; a face is the edge-connected triangles sharing it&lt;/td&gt;
&lt;td&gt;A global face number&lt;/td&gt;
&lt;td&gt;Colours label any number of faces. Test parts needed 2 to 5 colours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surface parameters (plane, axis, radius)&lt;/td&gt;
&lt;td&gt;Not stored; the reader fits them&lt;/td&gt;
&lt;td&gt;A parameter table&lt;/td&gt;
&lt;td&gt;No room without a trailer. Fitting is cheap once the faces are known, and a reader must verify anyway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CAD vertices&lt;/td&gt;
&lt;td&gt;3 bits per triangle, one per corner&lt;/td&gt;
&lt;td&gt;A separate vertex list&lt;/td&gt;
&lt;td&gt;No room. Needed where only two faces meet but a line runs into an arc&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detecting edits&lt;/td&gt;
&lt;td&gt;CRC-32 of the triangle data in the header&lt;/td&gt;
&lt;td&gt;No check&lt;/td&gt;
&lt;td&gt;Stale labels on an edited mesh would mislead the converter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Labelled" flag&lt;/td&gt;
&lt;td&gt;Bit 7, and a labelled triangle is written first&lt;/td&gt;
&lt;td&gt;A separate guard&lt;/td&gt;
&lt;td&gt;Bit 7 of the first attribute byte also defeats the admesh text misread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bit 15&lt;/td&gt;
&lt;td&gt;Always 0&lt;/td&gt;
&lt;td&gt;Free use&lt;/td&gt;
&lt;td&gt;VisCAM-style viewers read bit 15 = 1 as a colour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trust&lt;/td&gt;
&lt;td&gt;Every label is checked against tolerance&lt;/td&gt;
&lt;td&gt;Believe the file&lt;/td&gt;
&lt;td&gt;A wrong or stale label must not produce a wrong solid&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  6. Does it help?
&lt;/h2&gt;

&lt;p&gt;Labels helped most on a coarse mesh and on a part with a sphere and a rounded edge. On a clean, fine mesh of flats and cylinders they changed little.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsbagxj08pqaog2cn0hbz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsbagxj08pqaog2cn0hbz.webp" alt="STEP face counts for three test parts from a plain STL and from its hardpoint twin, with the face count of the original CAD model marked" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Volume error is the STEP solid against the original CAD solid (bracket 24,490.97 mm³, knob 12,347.01 mm³). Converter: STL2STEP 0.2.0-wip, Classic engine, default settings.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Triangles&lt;/th&gt;
&lt;th&gt;Plain: STEP faces&lt;/th&gt;
&lt;th&gt;Plain: volume error&lt;/th&gt;
&lt;th&gt;Plain: max deviation&lt;/th&gt;
&lt;th&gt;Hardpoint: STEP faces&lt;/th&gt;
&lt;th&gt;Hardpoint: volume error&lt;/th&gt;
&lt;th&gt;Hardpoint: max deviation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bracket, 0.05 mm mesh&lt;/td&gt;
&lt;td&gt;1,180&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;−0.013 %&lt;/td&gt;
&lt;td&gt;0.050 mm&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;+0.001 %&lt;/td&gt;
&lt;td&gt;0.049 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same bracket, coarse 0.3 mm mesh&lt;/td&gt;
&lt;td&gt;578&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;−0.075 %&lt;/td&gt;
&lt;td&gt;0.109 mm&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;+0.002 %&lt;/td&gt;
&lt;td&gt;0.086 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knob, 0.05 mm mesh&lt;/td&gt;
&lt;td&gt;2,958&lt;/td&gt;
&lt;td&gt;1,103&lt;/td&gt;
&lt;td&gt;−0.18 %&lt;/td&gt;
&lt;td&gt;0.129 mm&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;+0.007 %&lt;/td&gt;
&lt;td&gt;0.060 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;On the coarse bracket the plain conversion found 55 "flat" faces where the CAD model has 7.&lt;/li&gt;
&lt;li&gt;On the knob, the plain conversion took 14 s and the labelled one 2 s. The converter's second engine showed the same pattern in face counts: 21 to 17, 125 to 17 and 77 to 8.&lt;/li&gt;
&lt;li&gt;The CAD models have 16 faces (bracket) and 4 (knob), so even with labels the converter returns slightly more than the original. Cone and torus faces are labelled but the converter fits them as freeform surfaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The knob in the converter's own 3D preview, from the plain STL and from its hardpoint twin (same triangles; blue = flat, green = cylinder or sphere, grey = freeform surface, dark = left as facets):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plain STL: 1,103 faces&lt;/th&gt;
&lt;th&gt;Hardpoint STL: 8 faces&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo8yjh82mrtjp57v985e1.webp" alt="The knob converted from a plain STL: many flat strips and a dark faceted area" width="800" height="610"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F455d0jrnqihjla5ijo06.webp" alt="The knob converted from its hardpoint twin: one cylinder, one sphere, a smooth rounded edge" width="800" height="610"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Other measurements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Colours needed:&lt;/strong&gt; 2 on the knob, 3 on the bracket's 16 faces, 5 on an organic part's 69 fitted regions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Round trip:&lt;/strong&gt; the reference reader rebuilt exactly the faces, types and hardpoints written, on all three parts (25 of 25 and 4 of 4 CAD vertices).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loaders:&lt;/strong&gt; numpy-stl returned triangles identical to the plain twin; trimesh loaded each file as watertight; the admesh text test classed each as binary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain inputs unchanged:&lt;/strong&gt; five parts on the Classic engine and four on the second engine gave the same face counts, deviation and volume as before the feature was added.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writer speed, pure Python:&lt;/strong&gt; 4,894 triangles in 0.1 s and 15,384 in 0.2 s. The whole-body fallback added 0.4 s and 2.0 s. Measured on a cloud machine, not on a Core i3.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. What works, and what doesn't yet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Tested here&lt;/th&gt;
&lt;th&gt;Simulation or stand-in only&lt;/th&gt;
&lt;th&gt;Not tested / unknown&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Format&lt;/td&gt;
&lt;td&gt;Write, read, tamper check, &lt;code&gt;verify&lt;/code&gt;, &lt;code&gt;strip&lt;/code&gt;; examples pass&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loaders&lt;/td&gt;
&lt;td&gt;numpy-stl, trimesh; admesh text test (logic copied from the source)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;The slicer programs themselves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FreeCAD export&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Logic against a stand-in filled with OpenCASCADE geometry&lt;/td&gt;
&lt;td&gt;Real FreeCAD (0.20 to current); whether older versions accept the meshing call used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fusion export&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Per-face and whole-body routes against a stand-in&lt;/td&gt;
&lt;td&gt;Real Fusion; whether per-face meshes close; winding on reversed faces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Converter&lt;/td&gt;
&lt;td&gt;Three parts, two engines&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Real downloaded models; large meshes (labels tested up to 5,120 triangles)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Systems&lt;/td&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Windows, macOS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Colour-aware viewers&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Whether a viewer using the Magics convention tints faces without &lt;code&gt;COLOR=&lt;/code&gt; in the header&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Known limits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any program that re-saves the STL writes its own header and zero attribute fields, so the labels vanish. That is also why the file falls back to a plain STL, which is the safe direction.&lt;/li&gt;
&lt;li&gt;Text (ASCII) STL cannot carry labels.&lt;/li&gt;
&lt;li&gt;More than 32 faces that all touch each other cannot be coloured; the writer refuses with an error. No test part has hit this.&lt;/li&gt;
&lt;li&gt;Shapes with shared faces (FreeCAD compsolids) may give a segment count that differs from the face count; types are then written as unknown.&lt;/li&gt;
&lt;li&gt;Fusion bodies inside occurrences are exported in their own component's coordinates, not the assembly's.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Defensive disclosure: the hardpoint STL and variations
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This section is written to be prior art. It describes the method and its variations in enough detail for a person skilled in CAD programming and mesh file formats to build them. I dedicate the methods described in this paper to the public, and I will not seek patent protection for them. Where a variation is untested, it says so; untested variations are disclosed as possibilities, not as results.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  8.1 The core method
&lt;/h3&gt;

&lt;p&gt;A method of storing computer-aided-design face information in a triangle-mesh file in the binary STL format, such that the file remains a valid binary STL, comprising:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keep the file a binary STL.&lt;/strong&gt; Write an 80-byte header, a 4-byte little-endian triangle count, and for each triangle 50 bytes: a normal (3 × float32), three corners (9 × float32) and a 2-byte attribute word. The file size is exactly 84 + 50 × the triangle count. The geometry bytes are those a conventional export of the same mesh would give.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put the file-level facts in the header.&lt;/strong&gt; Begin the 80-byte header with an ASCII marker and a version number, followed by space-separated &lt;code&gt;key=value&lt;/code&gt; fields: units, mesh tolerance, mesh angle, a checksum of the geometry bytes, and the writer's name. The header does not begin with the word &lt;code&gt;solid&lt;/code&gt;. Readers ignore keys they do not know.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put the per-triangle facts in the attribute word.&lt;/strong&gt; For each triangle, store (a) a face colour in 5 bits, (b) a "labelled" flag, (c) a surface-type code in 3 bits, and (d) three bits flagging which of the triangle's three corners are CAD vertices (hardpoints). Leave remaining bits as reserved zeros, and keep the most significant bit zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify faces by colouring, not numbering.&lt;/strong&gt; Assign each CAD face a colour so that any two faces whose triangles share an edge have different colours. A face is then recovered as a set of edge-connected triangles of equal colour. A small number of colours labels any number of faces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make labels checkable.&lt;/strong&gt; The checksum covers the 48 geometry bytes of every triangle in file order, so a reader detects that the mesh was changed after labelling, and then treats the file as an unlabelled STL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make labels hints.&lt;/strong&gt; A reader checks each labelled face against its own tolerance and falls back to unaided detection for any face that fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  8.2 The reference writer (as built)
&lt;/h3&gt;

&lt;p&gt;The reference writer is &lt;code&gt;hstl.encode&lt;/code&gt;, about 100 lines of Python (standard library only), so it can run inside FreeCAD's and Fusion's own interpreters.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Round and weld.&lt;/strong&gt; Round every coordinate to float32 (after an optional scale, e.g. ×10 for a program working in centimetres). Merge points closer than a welding tolerance, by default 2 × 10⁻⁷ of the part's bounding-box diagonal (at least 10⁻⁹). A CAD kernel gives the two faces along an edge points that differ in the last digits, so exact matching leaves the mesh open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop collapsed triangles.&lt;/strong&gt; A triangle with two corners welded together is left out, and the writer returns which input triangle each output position holds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the mesh.&lt;/strong&gt; Count edges used by one triangle (open) and edges used by more than two, or by two triangles in the same direction (bad). Report the mesh as watertight when there are none.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Colour the faces.&lt;/strong&gt; Build a graph whose nodes are faces and whose edges join faces sharing a mesh edge. Colour it greedily in smallest-last order: repeatedly remove the node of lowest remaining degree, then assign colours in reverse removal order, each the smallest not used by a neighbour. Refuse, with an error, if 32 colours are not enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flag the hardpoints.&lt;/strong&gt; For each CAD vertex, find the welded mesh point nearest to it within 50 welding tolerances; set the flag bit of every triangle corner using that point. Report how many vertices found no point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put a labelled triangle first.&lt;/strong&gt; Swap one labelled triangle into position 0. Its attribute word has bit 7 set, which makes the sniffing test of section 1.3 see a byte above 127 near the start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the records.&lt;/strong&gt; Recompute each normal from the welded corners as a unit vector. Pack the attribute word as (colour) | 0x80 | (surface type &amp;lt;&amp;lt; 8) | (corner flags &amp;lt;&amp;lt; 11). Triangles without a face get zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checksum and header.&lt;/strong&gt; CRC-32 over the 48 geometry bytes of every record in order; write the header with it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  8.3 The reference reader
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recognise.&lt;/strong&gt; Read the 84-byte prefix. The file is a hardpoint STL if the header starts with the marker, the file size is 84 + 50 × count, and the version is known. Otherwise treat it as a plain STL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify.&lt;/strong&gt; Recompute the checksum. On a mismatch, ignore the labels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weld&lt;/strong&gt; corners with identical float32 coordinates (a writer has already welded within tolerance, so exact matching suffices).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group&lt;/strong&gt; triangles that share an edge and have the same colour into faces. A triangle with the labelled flag clear belongs to no face.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type&lt;/strong&gt; each face by majority vote over its triangles' type codes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collect&lt;/strong&gt; the corners flagged as hardpoints.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  8.4 How a converter should use the labels
&lt;/h3&gt;

&lt;p&gt;This is the behaviour of the converter I measured with, written as a recipe:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the header gives units other than millimetres and the user did not choose units, use the file's.&lt;/li&gt;
&lt;li&gt;Load and clean the mesh as usual. Skip labels if the mesh was reduced first.&lt;/li&gt;
&lt;li&gt;Match each cleaned triangle to the file's nearest triangle centre. Require at least 98 % to match within 0.25 × the header tolerance, otherwise ignore the labels.&lt;/li&gt;
&lt;li&gt;Handle each face by its type:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;If it passes&lt;/th&gt;
&lt;th&gt;If it fails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Plane&lt;/td&gt;
&lt;td&gt;Least-squares plane; every corner within 1.5 × tolerance&lt;/td&gt;
&lt;td&gt;A true plane&lt;/td&gt;
&lt;td&gt;Unaided detection inside that face&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cylinder, sphere&lt;/td&gt;
&lt;td&gt;The usual fit; if its sanity rules reject it, a least-squares fit of the labelled kind with every corner within tolerance&lt;/td&gt;
&lt;td&gt;An analytic cylinder or sphere&lt;/td&gt;
&lt;td&gt;Unaided detection inside that face&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cone, torus, freeform&lt;/td&gt;
&lt;td&gt;None up front&lt;/td&gt;
&lt;td&gt;Freeform patch fitting, without a search for primitives&lt;/td&gt;
&lt;td&gt;The existing behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keep as facets&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Kept as exact triangles&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown or unlabelled&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Unaided detection inside that area&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Map each hardpoint to the nearest mesh vertex within tolerance, and split border curves there, so a line running into an arc is fitted as a line and an arc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second cylinder check exists because the usual fit rejects shallow rounds, which it cannot tell from flat strips. A CAD label can.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.5 Getting the labels out of CAD programs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;FreeCAD&lt;/th&gt;
&lt;th&gt;Fusion&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mesh&lt;/td&gt;
&lt;td&gt;One mesh of the whole shape with one segment per face: &lt;code&gt;MeshPart.meshFromShape(Shape, LinearDeflection, AngularDeflection, Relative=False, Segments=True)&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Each face meshed on its own through &lt;code&gt;face.meshManager.createMeshCalculator()&lt;/code&gt;, with a surface tolerance (cm) and a maximum normal deviation (radians)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Triangle to face&lt;/td&gt;
&lt;td&gt;Segment &lt;em&gt;i&lt;/em&gt; is face &lt;em&gt;i&lt;/em&gt; in explorer order (checked in FreeCAD's source)&lt;/td&gt;
&lt;td&gt;Known by construction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surface type&lt;/td&gt;
&lt;td&gt;Class name of &lt;code&gt;face.Surface&lt;/code&gt; (Plane, Cylinder, Sphere, Cone, Toroid; anything else freeform)&lt;/td&gt;
&lt;td&gt;Last part of &lt;code&gt;face.geometry.objectType&lt;/code&gt; (Plane, Cylinder, Sphere, Cone, Torus; anything else freeform)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardpoints&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shape.Vertexes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;body.vertices&lt;/code&gt;, each &lt;code&gt;.geometry&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coordinates&lt;/td&gt;
&lt;td&gt;mm; the object's global placement is applied&lt;/td&gt;
&lt;td&gt;cm × 10; the body's own component coordinates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If something is off&lt;/td&gt;
&lt;td&gt;Segment count differs from the face count: faces are still labelled, types are written as unknown&lt;/td&gt;
&lt;td&gt;Per-face meshes don't close: mesh the whole body and give each triangle the face of the per-face triangle its middle point lies on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The whole-body fallback uses true point-to-triangle distance (nearest centre mislabelled about 1 %, section 4.2). It searches a uniform grid of cells, in growing shells, until the nearest triangle found is closer than the edge of the searched block.&lt;/p&gt;

&lt;p&gt;FreeCAD's source shows that &lt;code&gt;meshFromShape&lt;/code&gt; runs one incremental mesh over the whole shape, reads the triangulation back face by face in explorer order, flips reversed faces, and with &lt;code&gt;Segments=True&lt;/code&gt; adds one segment per face. For Fusion, whether per-face meshes share identical points along a common edge is not stated in Autodesk's pages; the writer reports open and wrongly oriented edges and the add-in switches to the whole-body route when there are any.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.6 Variations (disclosed; mostly untested)
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Where the bits go&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the reserved bits (5, 6 and 14) for: a "smooth border" flag for faces that meet tangentially, such as fillets; a body number for multi-body files; or a per-triangle "keep sharp normal" flag. Raise the version number.&lt;/li&gt;
&lt;li&gt;Widen the face colour to 6 or 7 bits, or the surface type to 4 bits, by taking reserved bits. Use bit 15 only if no colour-convention viewer is a concern.&lt;/li&gt;
&lt;li&gt;Flag edges (3 bits per triangle, one per side) instead of corners, to mark sharp or tangent edges.&lt;/li&gt;
&lt;li&gt;Identify a face by a run of consecutive triangles in file order plus a run-length code in the attribute words, instead of by colour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Carrying more than flags&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spread a small parameter record (a plane's normal and offset, a cylinder's axis, point and radius) across the attribute words of consecutive triangles of a face, 16 bits at a time, with the surface type saying how many triangles carry it. The reader must still verify it against the mesh.&lt;/li&gt;
&lt;li&gt;Store a compressed STEP, IGES or other CAD file across the attribute words when it fits (section 4.3), with a header key saying so, and fall back to labels when it doesn't. The room is 2 bytes per triangle, so this suits fine meshes of simple parts least, and coarse meshes of complex parts least.&lt;/li&gt;
&lt;li&gt;Store a second checksum over the attribute words, or a checksum of the labels only, so a program that edits colours but not geometry can be told apart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Header&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Further &lt;code&gt;key=value&lt;/code&gt; fields: part name, source file name, body count, a flag that hardpoints include two-face vertices, a creation date. Readers ignore keys they don't know.&lt;/li&gt;
&lt;li&gt;Another checksum (CRC-64, SHA-256 truncated) in place of CRC-32; hash the attribute words too.&lt;/li&gt;
&lt;li&gt;A marker after the 80-byte header, such as a first record whose attribute word is the format marker, for programs that rewrite headers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Writers&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any program that knows the CAD faces and can mesh them: CadQuery, build123d, Onshape, SolidWorks, Blender (from face groups), OpenSCAD (from its own object tree).&lt;/li&gt;
&lt;li&gt;A STEP-to-hardpoint-STL command: mesh a STEP file with OpenCASCADE and read the triangulation back face by face. No CAD plug-in needed.&lt;/li&gt;
&lt;li&gt;A converter that, after fitting a mesh, writes the labels back into the input's attribute words, copying the triangles byte for byte. (I built this; after converting, the labelled copy converted again to the same result. 12 faces on a bracket, 136 on an organic part.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Readers&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A viewer that colours a file by face or surface type, or checks a file against the format.&lt;/li&gt;
&lt;li&gt;A slicer that uses the faces, for example to give round holes different perimeter settings from flat walls.&lt;/li&gt;
&lt;li&gt;Tools that strip the labels (&lt;code&gt;hstl.py strip&lt;/code&gt; writes a plain STL with identical triangles).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Other carriers (untested)&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per-face groups in OBJ or PLY, or per-triangle properties in 3MF, to carry the same facts. Those formats can hold groups natively; the point of this paper's method is that an unmodified STL carries them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8.7 The reference implementation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;hstl.py&lt;/code&gt;&lt;/strong&gt; (about 600 lines, standard library only): &lt;code&gt;pack_word&lt;/code&gt;, &lt;code&gt;unpack_word&lt;/code&gt;, &lt;code&gt;make_header&lt;/code&gt;, &lt;code&gt;parse_header&lt;/code&gt;, &lt;code&gt;geometry_crc&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;decode&lt;/code&gt;, &lt;code&gt;colour&lt;/code&gt;, &lt;code&gt;encode&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;transfer_faces&lt;/code&gt;, &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;verify&lt;/code&gt;, &lt;code&gt;strip&lt;/code&gt;. Command line: &lt;code&gt;python hstl.py info|verify part.stl&lt;/code&gt;, &lt;code&gt;python hstl.py strip part.stl plain.stl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FreeCAD workbench&lt;/strong&gt; (&lt;code&gt;HardpointSTL&lt;/code&gt;): &lt;code&gt;hardpoint_fc.py&lt;/code&gt; (export logic, no GUI code) and &lt;code&gt;hardpoint_stl_fc.py&lt;/code&gt; (the command and dialog).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fusion add-in&lt;/strong&gt; (&lt;code&gt;HardpointSTL&lt;/code&gt;): &lt;code&gt;hardpoint_fusion.py&lt;/code&gt; (no &lt;code&gt;adsk&lt;/code&gt; imports) and &lt;code&gt;HardpointSTL.py&lt;/code&gt; (the command).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests, examples and illustration scripts&lt;/strong&gt; as in section 12.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Appendix A shows a real header and records.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. What I'd test next (feel free to use this plan)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Slicers.&lt;/strong&gt; Load &lt;code&gt;bracket_hardpoint.stl&lt;/code&gt; and &lt;code&gt;bracket_plain.stl&lt;/code&gt; into Cura, PrusaSlicer, OrcaSlicer and Bambu Studio and compare the sliced G-code. If they differ, the format has failed requirement 2 and I want to know.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FreeCAD.&lt;/strong&gt; Run &lt;code&gt;tests/freecad_selfcheck.py&lt;/code&gt; in FreeCAD (0.21, 1.0, 1.1) and send the output. Then export a part you actually modelled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fusion.&lt;/strong&gt; Run &lt;em&gt;Export hardpoint STL&lt;/em&gt; and note whether it took the per-face or the whole-body route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real models.&lt;/strong&gt; Export real parts from CAD and compare converter results with and without labels. All my test parts were built for the tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Colour-aware viewers.&lt;/strong&gt; Open a labelled file in a viewer that shows per-triangle STL colours and see whether faces get tinted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cone and torus.&lt;/strong&gt; A converter that builds cone and torus faces from the labels should turn countersinks and rounded edges into one exact face each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Big meshes.&lt;/strong&gt; Time the writer on a 500,000-triangle export on a low-end machine, and write a native version only if it is slow.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  10. Working together
&lt;/h2&gt;

&lt;p&gt;Nothing here came from one person working alone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The readers I could study:&lt;/strong&gt; OrcaSlicer's admesh code, Uranium's STL reader and trimesh are all public, and I could read them. The person who reported OrcaSlicer issue 15993 handed me the reason bit 7 is in the format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The colour conventions&lt;/strong&gt; of VisCAM, SolidView and Materialise Magics decided what bit 15 must not be.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The CAD programs' own source and documentation:&lt;/strong&gt; FreeCAD is open source and its mesher is readable. Autodesk's API pages told me what Fusion would do, and where they stay quiet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Earlier mesh-to-STEP tools:&lt;/strong&gt; stlToSolid and breptile showed what is possible without any hints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What I added:&lt;/strong&gt; putting these pieces together, building the exporters, measuring the result, and writing down every way it went wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Collaboration in the open does three things a patent never will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It's faster.&lt;/strong&gt; One person's dead end saves everyone else the trip. Checking whether a STEP file would fit cost me an afternoon of measuring. With this paper it costs you a table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It keeps the idea free.&lt;/strong&gt; A published idea counts as prior art. The more of us write down what we try, with dates and enough detail to rebuild it, the harder it is for anyone to claim what hobbyists worked out together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It gives better answers.&lt;/strong&gt; I only tested three parts. More people exporting, converting and comparing results will tell us far more.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;An open invitation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run the checks in section 9&lt;/strong&gt; and post your results, good or bad, as an issue or discussion on the repository. Photos of broken meshes are especially welcome. Failure is data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write an exporter&lt;/strong&gt; for the CAD program you use. &lt;code&gt;hstl.encode&lt;/code&gt; takes triangles, a face number per triangle, a type per face and a list of CAD vertices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a reader&lt;/strong&gt;, or make a converter that uses the labels better than mine does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write up your own experiments&lt;/strong&gt; the same way: dated, public, enough detail to rebuild them. Link back here and I'll link to you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everyone who contributes gets credited in the next version.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. How this was made
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;My part:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the idea, the six requirements in section 1.2 and the rule that a labelled file must stay a normal STL;&lt;/li&gt;
&lt;li&gt;the instruction to research first, and the question about hiding a STEP file;&lt;/li&gt;
&lt;li&gt;deciding what to publish, and when.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The AI assistant's part&lt;/strong&gt; (working to that design):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the research into how readers behave, the format design, the code, the tests, the measurements and the drawings;&lt;/li&gt;
&lt;li&gt;the first draft of this write-up;&lt;/li&gt;
&lt;li&gt;its share of the mistakes (1 to 6 in section 4.2).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every measurement in this paper was made by running the code in this repository, or the converter named in section 6, on a Linux cloud machine between 4 and 10 October 2026. None was made on real FreeCAD, Fusion or slicer software.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Code, data and how to reproduce
&lt;/h2&gt;

&lt;p&gt;Two repositories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This paper:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/a-point-of-contention" rel="noopener noreferrer"&gt;&lt;code&gt;a-point-of-contention&lt;/code&gt;&lt;/a&gt;. Its front page is this paper, generated from &lt;code&gt;paper/paper.md&lt;/code&gt;. It also holds the reference reader and writer (&lt;code&gt;code/hstl.py&lt;/code&gt;, Python 3, standard library only), the four example files (&lt;code&gt;data/&lt;/code&gt;: two parts, each as a hardpoint STL and as a plain STL with the same triangles), the full research notes (&lt;code&gt;notes/DESIGN_NOTES.md&lt;/code&gt;) and the specification (&lt;code&gt;notes/HARDPOINT_STL.md&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The program:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/hardpoint-stl" rel="noopener noreferrer"&gt;&lt;code&gt;hardpoint-stl&lt;/code&gt;&lt;/a&gt;. The FreeCAD workbench and Fusion add-in (&lt;code&gt;plugins/&lt;/code&gt;), the installers, the tests (&lt;code&gt;test_hstl.py&lt;/code&gt; and &lt;code&gt;test_install.py&lt;/code&gt; use the standard library only; &lt;code&gt;test_hardpoint_cad.py&lt;/code&gt; needs &lt;code&gt;cadquery-ocp&lt;/code&gt;, &lt;code&gt;numpy&lt;/code&gt;, &lt;code&gt;trimesh&lt;/code&gt;, and optionally &lt;code&gt;numpy-stl&lt;/code&gt;; &lt;code&gt;freecad_selfcheck.py&lt;/code&gt; runs inside real FreeCAD) and the scripts that regenerate the example files and drawings (&lt;code&gt;tools/make_examples.py&lt;/code&gt;, &lt;code&gt;tools/make_illustrations.py&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not included in either:&lt;/strong&gt; the converter I measured with. The numbers in section 6 can't be re-run from these repositories. The format checks, round trips and loader checks can.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  13. Licence, and keeping it free
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text and figures:&lt;/strong&gt; &lt;a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="noopener noreferrer"&gt;Creative Commons Attribution-ShareAlike 4.0&lt;/a&gt;. Anyone may copy, share and adapt it. Adapted versions must credit this paper and stay under the same licence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://polyformproject.org/licenses/noncommercial/1.0.0/" rel="noopener noreferrer"&gt;PolyForm Noncommercial 1.0.0&lt;/a&gt;. Free for non-commercial use. It is source-available, not open source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The methods:&lt;/strong&gt; dedicated to the public (section 8). A licence on text or code can't stop anyone &lt;em&gt;using&lt;/em&gt; a method, and I don't want it to. The point of publishing is that the format stays free for everyone to implement, including commercially, in their own code: from today's date this paper is a dated public description of it.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  14. Disclaimers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Untested in the real programs.&lt;/strong&gt; Nothing here was run in real FreeCAD, Fusion or a slicer. Statements about what those programs do come from their source code or documentation, as cited, and may be wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measurements.&lt;/strong&gt; The results in section 6 come from three test parts built for the tests and one converter. They are not a general claim about the format or about anyone else's converter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prior art.&lt;/strong&gt; My search for earlier work was informal (section 2). Nothing here is a statement that the idea is new, or about the validity of anyone's rights, and nothing here is legal advice. Whether and how a defensive publication counts as prior art is a question for a patent attorney.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other people's work.&lt;/strong&gt; Product, company and file-format names belong to their owners and are used only to identify them. This project is not affiliated with any of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your files.&lt;/strong&gt; The exporters write a new file where you choose and don't change your CAD document. A hardpoint STL is meant to be an ordinary STL; if you find one that isn't, please say so before you print it.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  15. How to cite
&lt;/h2&gt;

&lt;p&gt;Armstrong, M. (2026). &lt;em&gt;A POINT of contention: STL to STEP conversion shouldn't be that HARD. A proposed hybrid STL format that is CAD-friendly and slicer-compatible. A hobbyist's field notes on the hardpoint STL, including a defensive publication of the format.&lt;/em&gt; Version 1.0, 15 October 2026. &lt;a href="https://github.com/The-Dorkknight/a-point-of-contention" rel="noopener noreferrer"&gt;https://github.com/The-Dorkknight/a-point-of-contention&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;OrcaSlicer, &lt;code&gt;deps_src/admesh/stlinit.cpp&lt;/code&gt; (binary or text test, count from file size). &lt;a href="https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer/main/deps_src/admesh/stlinit.cpp" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer/main/deps_src/admesh/stlinit.cpp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OrcaSlicer issue 15993, small binary STLs imported as empty objects. &lt;a href="https://github.com/OrcaSlicer/OrcaSlicer/issues/15993" rel="noopener noreferrer"&gt;https://github.com/OrcaSlicer/OrcaSlicer/issues/15993&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ultimaker Uranium, &lt;code&gt;STLReader.py&lt;/code&gt; (Cura loads STL through numpy-stl). &lt;a href="https://raw.githubusercontent.com/Ultimaker/Uranium/main/plugins/FileHandlers/STLReader/STLReader.py" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/Ultimaker/Uranium/main/plugins/FileHandlers/STLReader/STLReader.py&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;trimesh, &lt;code&gt;exchange/stl.py&lt;/code&gt; (exact-length check, attribute field kept). &lt;a href="https://raw.githubusercontent.com/mikedh/trimesh/main/trimesh/exchange/stl.py" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/mikedh/trimesh/main/trimesh/exchange/stl.py&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;STL (file format), Wikipedia: binary layout, the &lt;code&gt;solid&lt;/code&gt; rule, the two colour conventions. &lt;a href="https://en.wikipedia.org/wiki/STL_(file_format)" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/STL_(file_format)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;FreeCAD source: &lt;code&gt;src/Mod/MeshPart/App/Mesher.cpp&lt;/code&gt;, &lt;code&gt;AppMeshPartPy.cpp&lt;/code&gt;; &lt;code&gt;src/Mod/Part/App/TopoShape.cpp&lt;/code&gt;, &lt;code&gt;BRepMesh.cpp&lt;/code&gt;, &lt;code&gt;Tools.cpp&lt;/code&gt;; &lt;code&gt;src/Mod/Mesh/App/Mesh.pyi&lt;/code&gt;. &lt;a href="https://github.com/FreeCAD/FreeCAD" rel="noopener noreferrer"&gt;https://github.com/FreeCAD/FreeCAD&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Autodesk Fusion API: &lt;a href="https://help.autodesk.com/cloudhelp/ENU/Fusion-360-API/files/TriangleMeshCalculator.htm" rel="noopener noreferrer"&gt;TriangleMeshCalculator&lt;/a&gt;, &lt;a href="https://help.autodesk.com/cloudhelp/ENU/Fusion-360-API/files/TriangleMesh.htm" rel="noopener noreferrer"&gt;TriangleMesh&lt;/a&gt;, &lt;a href="https://help.autodesk.com/cloudhelp/ENU/Fusion-360-API/files/BRepFace.htm" rel="noopener noreferrer"&gt;BRepFace&lt;/a&gt;, &lt;a href="https://help.autodesk.com/cloudhelp/ENU/Fusion-360-API/files/BRepBody.htm" rel="noopener noreferrer"&gt;BRepBody&lt;/a&gt;, &lt;a href="https://help.autodesk.com/cloudhelp/ENU/Fusion-360-API/files/FileDialog.htm" rel="noopener noreferrer"&gt;FileDialog&lt;/a&gt;, &lt;a href="https://help.autodesk.com/cloudhelp/ENU/Fusion-360-API/files/SelectionFilters_UM.htm" rel="noopener noreferrer"&gt;selection filters&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Autodesk forum, "How can I capture facets from a surface using the API" (per-face meshing example; silent on whether neighbouring faces line up). &lt;a href="https://forums.autodesk.com/t5/fusion-api-and-scripts-forum/how-can-i-capture-facets-from-a-surfaces-using-the-api/td-p/7070657" rel="noopener noreferrer"&gt;https://forums.autodesk.com/t5/fusion-api-and-scripts-forum/how-can-i-capture-facets-from-a-surfaces-using-the-api/td-p/7070657&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;stlToSolid, mesh to STEP by segmentation and primitive fitting. &lt;a href="https://github.com/Crypto69/stlToSolid" rel="noopener noreferrer"&gt;https://github.com/Crypto69/stlToSolid&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;breptile, mesh to STEP. &lt;a href="https://pypi.org/project/breptile/0.1.0/" rel="noopener noreferrer"&gt;https://pypi.org/project/breptile/0.1.0/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;numpy-stl. &lt;a href="https://github.com/WoLpH/numpy-stl" rel="noopener noreferrer"&gt;https://github.com/WoLpH/numpy-stl&lt;/a&gt; · trimesh. &lt;a href="https://github.com/mikedh/trimesh" rel="noopener noreferrer"&gt;https://github.com/mikedh/trimesh&lt;/a&gt; · cadquery-ocp (OpenCASCADE 8.0.1). &lt;a href="https://github.com/CadQuery/OCP" rel="noopener noreferrer"&gt;https://github.com/CadQuery/OCP&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pages and source files opened between 3 and 10 October 2026.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix A: a real hardpoint STL
&lt;/h2&gt;

&lt;p&gt;From &lt;code&gt;examples/bracket_hardpoint.stl&lt;/code&gt; (59,084 bytes = 84 + 50 × 1,180).&lt;/p&gt;

&lt;p&gt;The header, as written (80 bytes, padded with spaces):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HARDPOINT-STL 1 u=mm tol=0.05 ang=15 crc=e67609a0 by=cad-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The first triangle's 50-byte record, in hexadecimal:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 00 80 bf  00 00 00 00  00 00 00 00        normal  (-1, 0, 0)
00 00 f0 c1  00 00 80 c1  00 00 20 41        corner 1 (-30, -16, 10)
00 00 f0 c1  00 00 80 41  00 00 00 00        corner 2 (-30,  16,  0)
00 00 f0 c1  00 00 80 c1  00 00 00 00        corner 3 (-30, -16,  0)
80 39                                         attribute word 0x3980
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Reading the attribute word &lt;code&gt;0x3980&lt;/code&gt; = &lt;code&gt;0011 1001 1000 0000&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bits&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0–4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;face colour 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;labelled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8–10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;surface type 1: plane&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11–13&lt;/td&gt;
&lt;td&gt;7 (all three set)&lt;/td&gt;
&lt;td&gt;all three corners are CAD vertices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;as required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;python src/hstl.py info examples/bracket_hardpoint.stl&lt;/code&gt; prints:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hardpoint STL version 1, 1180 triangles, units mm, written by cad-example
  16 faces (1 cone, 8 cylinder, 7 plane), 25 hardpoints
  meshed to 0.05 mm, 15 degrees
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and &lt;code&gt;python src/hstl.py verify examples/bracket_hardpoint.stl&lt;/code&gt; ends with &lt;code&gt;RESULT: PASS&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  About this repository
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/blob/main/paper/paper.md" rel="noopener noreferrer"&gt;&lt;code&gt;paper/paper.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The paper (this page is generated from it)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/blob/main/paper/a-point-of-contention-v1.0.pdf" rel="noopener noreferrer"&gt;&lt;code&gt;paper/a-point-of-contention-v1.0.pdf&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The paper as a PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/tree/main/paper/figures" rel="noopener noreferrer"&gt;&lt;code&gt;paper/figures/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Figures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/blob/main/code/hstl.py" rel="noopener noreferrer"&gt;&lt;code&gt;code/hstl.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reference reader and writer (Python, standard library only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/tree/main/data" rel="noopener noreferrer"&gt;&lt;code&gt;data/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Example files, each as a hardpoint STL and a plain STL with the same triangles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/tree/main/notes" rel="noopener noreferrer"&gt;&lt;code&gt;notes/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The full research notes, the format specification, and how to archive this paper as prior art&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/tree/main/tools" rel="noopener noreferrer"&gt;&lt;code&gt;tools/&lt;/code&gt;&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/tree/main/tests" rel="noopener noreferrer"&gt;&lt;code&gt;tests/&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Rebuild the PDF, web page and README; checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/tree/main/docs" rel="noopener noreferrer"&gt;&lt;code&gt;docs/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The web version for GitHub Pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The FreeCAD workbench and Fusion add-in live in the separate&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/hardpoint-stl" rel="noopener noreferrer"&gt;&lt;code&gt;hardpoint-stl&lt;/code&gt;&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;To rebuild: &lt;code&gt;python3 tools/build_paper.py&lt;/code&gt; (pandoc and Chromium), then &lt;code&gt;python3 tools/make_readme.py&lt;/code&gt;.&lt;br&gt;
GitHub's &lt;strong&gt;Cite this repository&lt;/strong&gt; button uses &lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/blob/main/CITATION.cff" rel="noopener noreferrer"&gt;&lt;code&gt;CITATION.cff&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Join in:&lt;/strong&gt; run the checks in section 9 and post results with photos as an issue or discussion, write an exporter&lt;br&gt;
for another CAD program, make a converter that uses the labels better, or point out prior art I missed.&lt;br&gt;
Everyone who contributes gets credited in the next version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Licence:&lt;/strong&gt; paper text and figures &lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/blob/main/LICENSE.md" rel="noopener noreferrer"&gt;CC BY-SA 4.0&lt;/a&gt;; code in &lt;code&gt;code/&lt;/code&gt; and &lt;code&gt;tools/&lt;/code&gt;&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/a-point-of-contention/blob/main/LICENSE-CODE.md" rel="noopener noreferrer"&gt;PolyForm Noncommercial 1.0.0&lt;/a&gt; (source-available, not open source); the methods described are&lt;br&gt;
dedicated to the public as a defensive publication. Not legal advice.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Fin-ally Supported</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Tue, 20 Oct 2026 13:52:44 +0000</pubDate>
      <link>https://dev.to/open-madscience/fin-ally-supported-363n</link>
      <guid>https://dev.to/open-madscience/fin-ally-supported-363n</guid>
      <description>&lt;p&gt;&lt;strong&gt;Shark-fin supports for long, flat, tilted overhangs: a hobbyist's field guide and a defensive publication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Matthew Armstrong&lt;/strong&gt; (&lt;a href="https://github.com/The-Dorkknight" rel="noopener noreferrer"&gt;@The-Dorkknight&lt;/a&gt;)\&lt;br&gt;
Version 1.0 · First published 19 October 2026 · Text and figures: CC BY-SA 4.0 · Code: PolyForm Noncommercial 1.0.0\&lt;br&gt;
Repository: &lt;a href="https://github.com/The-Dorkknight/fin-ally-supported" rel="noopener noreferrer"&gt;https://github.com/The-Dorkknight/fin-ally-supported&lt;/a&gt; · Software: &lt;a href="https://github.com/The-Dorkknight/cura-shark-support" rel="noopener noreferrer"&gt;https://github.com/The-Dorkknight/cura-shark-support&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/blob/main/paper/fin-ally-supported-v1.0.pdf" rel="noopener noreferrer"&gt;PDF&lt;/a&gt;&lt;/strong&gt; · 🌐 &lt;a href="https://The-Dorkknight.github.io/fin-ally-supported/" rel="noopener noreferrer"&gt;Web page&lt;/a&gt; · 🔧 &lt;a href="https://github.com/The-Dorkknight/cura-shark-support" rel="noopener noreferrer"&gt;The plugin&lt;/a&gt; · About this repository&lt;/p&gt;

&lt;p&gt;This page is the full paper (the same text and figures as the PDF).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contents:&lt;/strong&gt; Why this paper exists · The problem · What already exists · Shark fins in one page · The method in detail · How it got here: the mistakes, in order · Numbers · Defensive disclosure: the method and its variations · Test plan (please print this) · Limitations and open questions · Fin-ally, a social species · Code, data and how to reproduce · How this was made · Licence, and keeping it free · Disclaimers · How to cite · References&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Read this first.&lt;/strong&gt; Every number in this paper comes from geometry code, slicer estimates or G-code. &lt;strong&gt;Nothing&lt;br&gt;
has been printed and broken yet.&lt;/strong&gt; Where a result is a slicer estimate or a simulation it says so. Section 8 is a&lt;br&gt;
test plan for anyone with a printer and a free evening.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Abstract
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7ddh7yox30uypbt1hnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7ddh7yox30uypbt1hnw.png" alt="One fin, side view, labelled" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Printing a long box tilted onto one of its long edges makes its walls strong along their length, but turns its base&lt;br&gt;
into a long, flat overhang at 30–55°. That is near, or just past, the angle where slicers start adding support, and over 150 mm it tends to sag and&lt;br&gt;
curl.&lt;br&gt;
Normal slicer supports fix it by filling the space under the whole face with a forest that welds itself to the&lt;br&gt;
part. This paper describes &lt;strong&gt;shark-fin supports&lt;/strong&gt;: a row of thin vertical plates that run &lt;em&gt;downhill&lt;/em&gt; under the face,&lt;br&gt;
follow its slope, touch it only at small teeth, stop short of the top with a blunt flat tip, and stand on a crossbar&lt;br&gt;
foot and a one-layer brim pad. They are cheap (on the test box, 1.68 m of filament and about 40 minutes for five&lt;br&gt;
fins, slicer estimate) and should come off in one piece each. The method is implemented as an open Cura plugin with a&lt;br&gt;
toolbar button that lets you click the face you want supported. Section 7 is a &lt;strong&gt;defensive disclosure&lt;/strong&gt;: the method&lt;br&gt;
and a long list of variations are published as prior art, to make it much harder for anyone to patent them and&lt;br&gt;
charge hobbyists for them. They are dedicated to the public.&lt;/p&gt;




&lt;h2&gt;
  
  
  0. Why this paper exists
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;To keep it free.&lt;/strong&gt; A published method is prior art, which makes it much harder for anyone to get a valid
patent on it later. This paper puts the shark fin, its teeth, its blunt top, its feet and its many cousins on the
public record with a date, so that a company filing on them later has this to get past first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;To write down the dead ends.&lt;/strong&gt; Every version so far had a mistake in it that looked like a good idea at the
time. Section 5 lists them, so nobody has to make them twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;To bring makers together.&lt;/strong&gt; A plugin is a tool; a paper is an invitation. Print the test plan, post the
results, fork the code, prove me wrong. Everyone who contributes gets credit in the next version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selfish reasons.&lt;/strong&gt; Publishing things like this tends to bring more interesting 3D printing tools and tricks
back to me. It also makes me roughly 0.3 % more employable per paper, which, compounded over a few more
papers, is a very efficient use of an evening.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. The problem
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Tilting for strength
&lt;/h3&gt;

&lt;p&gt;FDM parts are weakest between layers. A long box printed flat is strong along its length only in the layers&lt;br&gt;
themselves; printed on its end it is strong in the wrong direction and wobbly. A common compromise is to &lt;strong&gt;stand&lt;br&gt;
the box on one long edge and tilt it&lt;/strong&gt;, so the long walls run along the layers and the base and one side become&lt;br&gt;
sloping faces (Figure 1).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwb192pizam628nkkjyk9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwb192pizam628nkkjyk9.png" alt="The 35° test box and its fins" width="635" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. The 35° test box (grey, see-through) and its fins (orange).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The test part used throughout is a 150 × 40 × 25 mm box rotated 35° about one long bottom edge, so its base&lt;br&gt;
becomes a &lt;strong&gt;55° overhang&lt;/strong&gt; (in Cura's convention: 0° is a vertical wall, 90° a flat ceiling). It is deliberately&lt;br&gt;
boring. Boring parts make honest tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Why "it's under the angle" is not enough
&lt;/h3&gt;

&lt;p&gt;Slicers and rules of thumb treat overhangs up to about 45–55° as printable without support. That is true for a short&lt;br&gt;
ledge. Over a long face several things add up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each layer's edge sticks out a little further than the one below, and a long free edge cools unevenly and curls
upwards, which the nozzle then hits;&lt;/li&gt;
&lt;li&gt;the lowest part of the face is printed first and has nothing above it to hold it flat;&lt;/li&gt;
&lt;li&gt;a long, thin, freshly printed edge sags under its own weight and the drag of the nozzle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The usual result is a wavy, curled base, or a print knocked loose halfway through.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 What slicers offer
&lt;/h3&gt;

&lt;p&gt;Turning on normal supports fills the space under the whole base with support. It works, but it uses a lot of&lt;br&gt;
plastic and time, it bonds along every layer of the face, and on a tilted part it leaves a large scarred surface.&lt;br&gt;
Tree supports use less material but branch to many contact points and can be awkward to remove from a long flat&lt;br&gt;
face. Neither knows that this particular overhang only needs &lt;strong&gt;a few stiff ribs&lt;/strong&gt; to stop it curling.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What already exists
&lt;/h2&gt;

&lt;p&gt;This section is an informal prior-art search, not a professional one. It is here to give credit and to be clear&lt;br&gt;
about what is new.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slicer supports.&lt;/strong&gt; Cura, PrusaSlicer and OrcaSlicer offer grid/line supports, tree or "organic" supports, support
blockers and enforcers, painted supports, and "support meshes": any shape the user loads can be marked to print as
support (&lt;a href="https://help.prusa3d.com/en/article/support-material_1698/" rel="noopener noreferrer"&gt;Prusa Knowledge Base&lt;/a&gt;; &lt;a href="https://printago.io/guides/prusaslicer-supports" rel="noopener noreferrer"&gt;Printago guide&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand-drawn sacrificial ribs.&lt;/strong&gt; Designers have long added thin ribs to a model in CAD to hold up an overhang and
snapped them off afterwards (&lt;a href="https://www.hubs.com/knowledge-base/supports-3d-printing-technology-overview" rel="noopener noreferrer"&gt;Hubs overview of supports&lt;/a&gt;). That is the same idea as a fin, made by hand,
usually as a plain plate that bonds along its full edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ribbed support vaults&lt;/strong&gt; (Tricard, Claux and Lefebvre, &lt;em&gt;Computer Graphics Forum&lt;/em&gt;, 2019) grow rib-like internal
walls inside hollowed FFF prints to hold up internal overhangs (&lt;a href="https://www.unilim.fr/pages_perso/frederic.claux/blog/2019/2019-06-21_Ribbed_support_vaults.html" rel="noopener noreferrer"&gt;project page&lt;/a&gt;). They are walls that
support from below, but inside the object and merging into it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teeth on metal supports.&lt;/strong&gt; In metal powder-bed printing, block supports often meet the part through small
teeth, whose size, shape and penetration affect the strength of the support (&lt;a href="https://www.materialise.com/en/inspiration/articles/optimal-metal-supports" rel="noopener noreferrer"&gt;Materialise&lt;/a&gt;). The fins'
teeth borrow from that habit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;US 9,469,057 (3D Systems, priority 2012).&lt;/strong&gt; Claims a method of printing an FFF support whose upper portion
contacts the part at discrete "fine points", with a retract, lift, move, lower sequence between them
(&lt;a href="https://patents.justia.com/patent/9469057" rel="noopener noreferrer"&gt;Justia&lt;/a&gt;; &lt;a href="https://patents.google.com/patent/US9469057B2/en" rel="noopener noreferrer"&gt;Google Patents&lt;/a&gt;, which lists it as active until about 2034). Worth knowing:
in the top layers of a fin, each tooth tip is a small separate island in the slice, and slicers usually retract
and travel (and may lift) between islands. &lt;strong&gt;How this patent relates to Shark Support has not been assessed.&lt;/strong&gt;
If it matters to you, ask a patent professional; nothing here is legal advice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As far as this search found, nobody describes &lt;strong&gt;downhill fins generated automatically under a flat tilted face,&lt;br&gt;
contacting through teeth, cut short with a blunt top, braced by a crossbar foot and brim pad, with a click-to-pick&lt;br&gt;
face selection&lt;/strong&gt;. If you know of earlier work, please open an issue: the next version will credit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Shark fins in one page
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fho9by7bq0r71r23ed7c9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fho9by7bq0r71r23ed7c9.png" alt="Plan view of the fins" width="799" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each flat face that faces downwards:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Downhill.&lt;/strong&gt; Work out the direction a ball would roll down the face, flattened onto the bed. Fins run that way,
spaced evenly across the face (30 mm by default, 4 mm in from the sides). Figure 2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure.&lt;/strong&gt; Along each fin line, cast vertical rays up from the bed every 0.5 mm to find the part's underside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shape&lt;/strong&gt; each fin as a thin vertical plate (2.4 mm thick at the bed, 1.2 mm at the top) whose top edge sits 1 mm
under the part, with &lt;strong&gt;teeth every 3 mm&lt;/strong&gt; that reach up to 0.15 mm short of it. The &lt;strong&gt;first two teeth&lt;/strong&gt; from the
low end reach 0.1 mm &lt;em&gt;into&lt;/em&gt; the part, so the start of the overhang, where curling begins, is anchored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop early.&lt;/strong&gt; The fin follows the part only to &lt;strong&gt;85 %&lt;/strong&gt; of its height; the part bridges the rest. The fin ends
in a &lt;strong&gt;5 mm flat top&lt;/strong&gt;, then goes &lt;strong&gt;straight down&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stand it up&lt;/strong&gt; with a 4 mm high crossbar foot under the tip and a 4 mm wide, one-layer brim pad.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Print it as a solid little part&lt;/strong&gt; next to the model, not as Cura "support".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Figure 10. One fin of the test box, side view, drawn from the code's output and labelled.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The method in detail
&lt;/h2&gt;

&lt;p&gt;This section is written so that someone could rebuild the method from it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Finding the face
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Automatic mode.&lt;/strong&gt; Triangles of the model that face downwards are grouped into flat faces: two triangles join when&lt;br&gt;
they share an edge and their normals differ by less than 3°. A face qualifies when its overhang angle α (the angle&lt;br&gt;
between its outward normal and the horizontal, so 0° is a wall and 90° a ceiling) lies between 20° and 70°, its area&lt;br&gt;
is at least 300 mm², and it spans at least 15 mm downhill, measured in plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Picked mode.&lt;/strong&gt; The user clicks the face. The slicer reports the 3D point under the mouse (in Cura, its picking&lt;br&gt;
render pass). The plugin finds the triangle that contains the point (the projection of the point lies inside the&lt;br&gt;
triangle within a small tolerance, and the point is within 1 mm, or 1 % of the model size, of its plane), checks&lt;br&gt;
that it faces downwards and is not a flat ceiling, and uses the flat face that triangle belongs to. A picked face&lt;br&gt;
ignores the automatic angle and size limits: the user has said it needs help.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Direction and layout
&lt;/h3&gt;

&lt;p&gt;For a face with unit normal &lt;strong&gt;n&lt;/strong&gt;, gravity &lt;strong&gt;g&lt;/strong&gt; = (0, 0, −1) projected into the face's plane is&lt;br&gt;
&lt;strong&gt;d&lt;/strong&gt; = &lt;strong&gt;g&lt;/strong&gt; − (&lt;strong&gt;g&lt;/strong&gt;·&lt;strong&gt;n&lt;/strong&gt;)&lt;strong&gt;n&lt;/strong&gt;, the steepest descent. Its horizontal part, normalised, is the &lt;em&gt;downhill&lt;/em&gt; direction&lt;br&gt;
&lt;strong&gt;h&lt;/strong&gt;; &lt;strong&gt;c&lt;/strong&gt; = &lt;strong&gt;ẑ&lt;/strong&gt; × &lt;strong&gt;h&lt;/strong&gt; is &lt;em&gt;across&lt;/em&gt;. Projecting the face's corners onto &lt;strong&gt;c&lt;/strong&gt; gives its width; fins sit at&lt;br&gt;
evenly spaced positions across it, a side margin from each edge, at most a set spacing apart (one fin in the middle&lt;br&gt;
if the face is narrow). A flat ceiling has no downhill and is skipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Measuring the underside
&lt;/h3&gt;

&lt;p&gt;Along each fin line, at steps of 0.5 mm in &lt;strong&gt;h&lt;/strong&gt;, a vertical ray goes up from the bed. For a closed mesh the first&lt;br&gt;
surface a ray meets from below always faces downwards, so only downward triangles are indexed, in a uniform grid in&lt;br&gt;
plan. Each hit gives the height of the part above the bed and which triangle was hit. The fin keeps the &lt;strong&gt;longest&lt;br&gt;
continuous run&lt;/strong&gt; of samples that hit its own face and are at least the &lt;em&gt;start height&lt;/em&gt; (4 mm) above the bed, so it&lt;br&gt;
never climbs onto a neighbouring face or across a gap. Below the start height the face is short enough to print on&lt;br&gt;
its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 The top edge: relief, teeth and anchors
&lt;/h3&gt;

&lt;p&gt;Let s run along the fin from its low end s₀, and z(s) be the underside height. The fin's top edge is&lt;br&gt;
z(s) − &lt;em&gt;relief&lt;/em&gt; (1 mm). Teeth are placed from the low end: the first tooth's ramp starts at s₀, then one every&lt;br&gt;
&lt;em&gt;pitch&lt;/em&gt; (3 mm), and a last one near the end. Each tooth ramps up over 1 mm, stays flat for 0.6 mm, and ramps down.&lt;br&gt;
At its flat top it reaches z(s) − &lt;em&gt;gap&lt;/em&gt; (0.15 mm) or, for the first &lt;em&gt;N&lt;/em&gt; anchor teeth (2), z(s) + &lt;em&gt;overlap&lt;/em&gt;&lt;br&gt;
(0.1 mm, half of a 0.2 mm layer). The anchors therefore print touching the part's outer wall in the same layers&lt;br&gt;
(Figure 6); the other tips stop just short.&lt;/p&gt;

&lt;p&gt;Why teeth and not a plain edge: a plain edge 0.15 mm under a sloping face touches it along every layer. Teeth turn&lt;br&gt;
that into a dotted line, so the fin snaps off with a short pull and leaves small, evenly spaced marks.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.5 Stopping short, with a blunt top
&lt;/h3&gt;

&lt;p&gt;The fin follows the part only up to a height z_cut = (1 − &lt;em&gt;top cut&lt;/em&gt;) × the highest edge height it would otherwise&lt;br&gt;
reach (top cut 15 %, so 85 %). Teeth near the cut are flattened to z_cut so nothing pokes above it. From there the&lt;br&gt;
fin runs &lt;strong&gt;flat for 5 mm&lt;/strong&gt; (&lt;em&gt;tip length&lt;/em&gt;), then ends.&lt;/p&gt;

&lt;p&gt;Why: a sharp tip means the last layers of the fin are a few millimetres long or less. The printer brakes and&lt;br&gt;
accelerates for each tiny move, which is slow and prints badly. With a 5 mm flat top, no layer of the fin is shorter&lt;br&gt;
than 5 mm. And the top 15 % of a long tilted face is the part closest to its own upper edge, which is already held&lt;br&gt;
by the rest of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.6 The back edge
&lt;/h3&gt;

&lt;p&gt;By default the fin goes &lt;strong&gt;straight down&lt;/strong&gt; from the end of the flat top. Variants (Figure 9) lean the back out at a&lt;br&gt;
set angle (20° from vertical) for a wider base, or make it round (a quarter ellipse) or concave. Each point of the&lt;br&gt;
back is checked against the part above it and lowered if needed, and the back never goes below a 0.6 mm stub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fou57ij21xxivvjnlot32.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fou57ij21xxivvjnlot32.png" alt="Back-edge variants" width="799" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.7 Thickness
&lt;/h3&gt;

&lt;p&gt;The fin is a "ribbon" of columns along s. Each column has a bottom width (2.4 mm), a top width that tapers linearly&lt;br&gt;
with height from 2.4 to 1.2 mm, and a top height. Neighbouring columns are joined into a closed, outward-facing&lt;br&gt;
mesh. Thicker at the bed, where bending is worst; thin at the top, where the teeth are.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.8 Crossbar foot
&lt;/h3&gt;

&lt;p&gt;A bar across the fin, 2 mm thick and 4 mm high, its length ¼ of the fin's footprint (at least 10 mm), under the&lt;br&gt;
middle of the flat top, where the fin is tallest. Fins with a footprint over 100 mm get a second bar halfway&lt;br&gt;
between the low end and the 85 % cut.&lt;br&gt;
A bar is lowered if the part comes within the relief above it, and skipped if that leaves less than 1.5 mm.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.9 Brim pad
&lt;/h3&gt;

&lt;p&gt;A rectangle one first layer thick, 4 mm wider than the fin on every side, plus one around each crossbar. Where the&lt;br&gt;
part itself comes close to the bed at the low end, the pad is trimmed until the part is at least 1 mm above it, so&lt;br&gt;
it never welds to the part's own edge.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.10 Solid part, not "support"
&lt;/h3&gt;

&lt;p&gt;Cura can print any mesh as support, but its support settings are global: density, walls and pattern cannot be set&lt;br&gt;
per object, so a 1.2 mm fin printed as support would come out as a few sparse lines. And in the Cura releases&lt;br&gt;
checked, the setting called "support brim" prints a brim &lt;em&gt;inside&lt;/em&gt; support areas, not around them. So by default the&lt;br&gt;
fins are a &lt;strong&gt;separate ordinary object&lt;/strong&gt; with three per-object settings: infill 100 %, support off, and "union&lt;br&gt;
overlapping volumes" on, so a fin, its crossbar and its pad print as one piece. The gaps between fin and part are&lt;br&gt;
in the geometry itself. A "print as support mesh" switch is kept for comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.11 In the slicer
&lt;/h3&gt;

&lt;p&gt;In Cura the method is a &lt;strong&gt;toolbar button&lt;/strong&gt; under move, scale and rotate (Figure 7). Its panel has &lt;em&gt;Pick face&lt;/em&gt;,&lt;br&gt;
&lt;em&gt;Auto&lt;/em&gt;, &lt;em&gt;Remove all&lt;/em&gt; and &lt;em&gt;Settings&lt;/em&gt;. Picking a face that already has fins replaces them; &lt;em&gt;Auto&lt;/em&gt; replaces a model's&lt;br&gt;
earlier fins. All of it is undoable. For other slicers a command line tool writes the fins as an STL to load next to&lt;br&gt;
the part.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi7sovqgiwont7y7jhuii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi7sovqgiwont7y7jhuii.png" alt="Picking a face in Cura" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 7. Cura (from source) with the Shark Support panel open: a mouse click on the underside, sent by the test&lt;br&gt;
driver (xdotool, virtual display), made these fins.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj58d468u6im61rhlklar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj58d468u6im61rhlklar.png" alt="Fins in Cura" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 8. The same box after Auto, from behind: five fins with their feet and pads.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.12 All the numbers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Overhang angle range (auto)&lt;/td&gt;
&lt;td&gt;20–70°&lt;/td&gt;
&lt;td&gt;Which faces &lt;em&gt;Auto&lt;/em&gt; supports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smallest face, shortest slope (auto)&lt;/td&gt;
&lt;td&gt;300 mm², 15 mm&lt;/td&gt;
&lt;td&gt;Faces smaller than this are left alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fin spacing, side margin&lt;/td&gt;
&lt;td&gt;30 mm, 4 mm&lt;/td&gt;
&lt;td&gt;Across the face&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start height&lt;/td&gt;
&lt;td&gt;4 mm&lt;/td&gt;
&lt;td&gt;Fins start where the face is this high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thickness at bed / top&lt;/td&gt;
&lt;td&gt;2.4 / 1.2 mm&lt;/td&gt;
&lt;td&gt;Linear taper with height&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relief&lt;/td&gt;
&lt;td&gt;1 mm&lt;/td&gt;
&lt;td&gt;Gap under the part between teeth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tooth pitch, tip, ramp&lt;/td&gt;
&lt;td&gt;3, 0.6, 1 mm&lt;/td&gt;
&lt;td&gt;Tooth shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tip gap&lt;/td&gt;
&lt;td&gt;0.15 mm&lt;/td&gt;
&lt;td&gt;Ordinary teeth stop this far under the part&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anchor teeth, overlap&lt;/td&gt;
&lt;td&gt;2, 0.1 mm&lt;/td&gt;
&lt;td&gt;First teeth from the low end reach into the part&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top cut&lt;/td&gt;
&lt;td&gt;15 %&lt;/td&gt;
&lt;td&gt;Fin stops at 85 % of its height&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tip length&lt;/td&gt;
&lt;td&gt;5 mm&lt;/td&gt;
&lt;td&gt;Flat blunt top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Back&lt;/td&gt;
&lt;td&gt;vertical&lt;/td&gt;
&lt;td&gt;Or leaning (20°), round, concave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crossbar&lt;/td&gt;
&lt;td&gt;¼ of footprint × 4 mm high × 2 mm&lt;/td&gt;
&lt;td&gt;Under the tip; second one over 100 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brim pad&lt;/td&gt;
&lt;td&gt;4 mm wide, one first layer&lt;/td&gt;
&lt;td&gt;Around fin and crossbar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ray step&lt;/td&gt;
&lt;td&gt;0.5 mm&lt;/td&gt;
&lt;td&gt;Profile resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  5. How it got here: the mistakes, in order
&lt;/h2&gt;

&lt;p&gt;Most of the versions below were built, tested in code and looked at; two were design dead ends caught before the&lt;br&gt;
first release. Each taught something.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffa92aaolsfnf80s5sssa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffa92aaolsfnf80s5sssa.png" alt="Four versions of one fin" width="798" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3 is drawn with today's code and each version's settings, so tooth positions follow today's rule.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;0.1.0: the actual shark.&lt;/strong&gt; A round back edge, because it looked like a fin and seemed sturdier. It was
neither: the crossbar already stops the fin tipping over, and the round back was a strip of plastic sticking out
past the part. Measured later: 2.27 m of filament against 1.74 m without it (Figure 4).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.1.0: teeth centred along the fin.&lt;/strong&gt; The row of teeth was centred, so the first contact could sit up to 15 %
of the way in, leaving the start of the overhang, where curling begins, unheld. Fixed in 0.1.1 by starting at the
low end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before 0.1.0 (design): "support brim" is not a brim.&lt;/strong&gt; The plan was to use Cura's support brim for the 4 mm pad. Reading Cura's
settings showed it prints inside support, not around it. The pad became geometry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before 0.1.0 (design): fins as Cura support.&lt;/strong&gt; Printed at the global support density, a fin is a few lines. Fins became solid
objects (section 4.10).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.1.1: the triangle.&lt;/strong&gt; Straight back: 23 % less filament (27 % less fin volume). Still a sharp tip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.1.2: more teeth, two anchors.&lt;/strong&gt; Pitch halved from 6 to 3 mm. The first two teeth reach 0.1 mm into the part;
the rest stop 0.15 mm short (was 0.2). Almost free: 1.76 m instead of 1.74 m. With 3 mm pitch the valleys
between teeth are only 0.4 mm long, so the top edge is effectively a saw blade. If it grips too hard, 4 mm pitch
opens the valleys again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.2.0: the blunt top.&lt;/strong&gt; Looking at a Cura preview made the problem obvious: the tip of a triangle is a column
of tiny, slow layers. Cutting at 85 % with a 5 mm flat top fixed it and saved time: 1.68 m, about 40 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.2.0: the leaning back, measured and demoted.&lt;/strong&gt; The first sketch of the new shape had the back leaning out to
the bed. Measured, it cost more than going straight down (1.97 m, about 42 minutes). It is now an option for when
a wider base matters more than plastic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin plumbing, for other plugin authors:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Uranium's signals hold &lt;em&gt;weak&lt;/em&gt; references, so connecting a lambda silently does nothing. Connect a method.&lt;/li&gt;
&lt;li&gt;Cura caches "is this a support mesh" per object and only refreshes it when a setting change is announced;
setting it directly leaves the cache stale.&lt;/li&gt;
&lt;li&gt;A plugin's first log lines can appear before Cura's log file is open; log again once the app has started.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UM.Dialog&lt;/code&gt; takes only visual items as children: a &lt;code&gt;Connections&lt;/code&gt; object in the wrong place makes the whole
dialog fail to load.&lt;/li&gt;
&lt;li&gt;Toolbar icons are SVGs that Cura recolours by inserting a &lt;code&gt;fill&lt;/code&gt; attribute into the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; tag. An icon that
already has one there becomes invalid.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. Numbers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgx4but2c7g9dem0lypon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgx4but2c7g9dem0lypon.png" alt="Filament and time per version" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All from PrusaSlicer 2.7.2's command line on the fins alone: 0.2 mm layers, 0.4 mm nozzle, two perimeters,&lt;br&gt;
PrusaSlicer's default speeds. These are &lt;strong&gt;slicer estimates&lt;/strong&gt;, not stopwatch times.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Fins (5)&lt;/th&gt;
&lt;th&gt;Filament&lt;/th&gt;
&lt;th&gt;Time (estimate)&lt;/th&gt;
&lt;th&gt;Volume (code)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.1.0 round back&lt;/td&gt;
&lt;td&gt;sharp, to the top&lt;/td&gt;
&lt;td&gt;2.27 m&lt;/td&gt;
&lt;td&gt;48 min&lt;/td&gt;
&lt;td&gt;4.7 cm³&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.1.1 triangle&lt;/td&gt;
&lt;td&gt;sharp, to the top&lt;/td&gt;
&lt;td&gt;1.74 m&lt;/td&gt;
&lt;td&gt;42 min&lt;/td&gt;
&lt;td&gt;3.4 cm³&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.1.2 more teeth&lt;/td&gt;
&lt;td&gt;sharp, to the top&lt;/td&gt;
&lt;td&gt;1.76 m&lt;/td&gt;
&lt;td&gt;43 min&lt;/td&gt;
&lt;td&gt;3.5 cm³&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.2.0, leaning back&lt;/td&gt;
&lt;td&gt;blunt, 85 %&lt;/td&gt;
&lt;td&gt;1.97 m&lt;/td&gt;
&lt;td&gt;42 min&lt;/td&gt;
&lt;td&gt;4.0 cm³&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0.2.0 default&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;blunt, 85 %&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.68 m&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40 min&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.3 cm³&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Volumes are without brim pads and crossbars; filament and time include them. The 0.1.0 and 0.1.1 volumes are&lt;br&gt;
redrawn with today's code, so their tooth positions follow today's rule.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the slicer print what the code drew?&lt;/strong&gt; Figure 5 plots every extrusion in the real G-code within 0.5 mm of the&lt;br&gt;
middle fin. The fin is solid, follows the part with a stepped gap, stops below the top of the face with a flat top,&lt;br&gt;
and goes straight down at the back. Figure 6 zooms in on the low end: the anchor teeth's lines run into the part's&lt;br&gt;
outer wall, while further up there is a clear gap at every tooth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6547l9nwa49dt8fb740o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6547l9nwa49dt8fb740o.png" alt="G-code slice through the middle fin" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5. Every extrusion in the PrusaSlicer 2.7.2 G-code within 0.5 mm of the middle fin, seen from the side.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7evpwe5yf0pinrv6h0ug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7evpwe5yf0pinrv6h0ug.png" alt="G-code zoom on the anchor teeth" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 6. The low end of the same fin, zoomed in.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed of the code.&lt;/strong&gt; A 196,608-triangle version of the test box took about 2 s to process (1.6–2.3 s across&lt;br&gt;
runs) on the development machine; the 12-triangle box takes 0.02 s. Not yet timed on a 7th-generation Core i3, which is the target for&lt;br&gt;
everything in this project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A box on its corner.&lt;/strong&gt; Rolled a further 12° onto a corner, both faces next to the standing edge qualify, and the&lt;br&gt;
same code makes ten fins; with the anchors switched off, the closest tooth is exactly 0.15 mm under the part&lt;br&gt;
(Figure 11).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcjglpmix8rf6sarnmfis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcjglpmix8rf6sarnmfis.png" alt="Box on a corner" width="674" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 11. The box rolled 12° onto a corner: two faces, ten fins.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Defensive disclosure: the method and its variations
&lt;/h2&gt;

&lt;p&gt;The following are disclosed as part of this publication, alone and in any combination, for FDM/FFF and for any&lt;br&gt;
other layer-wise process where they make sense. They are dedicated to the public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.1 The core.&lt;/strong&gt; A support structure for a downward-facing surface of an object being built layer by layer,&lt;br&gt;
comprising one or more upright plates ("fins") generated from the object's geometry, each running substantially&lt;br&gt;
along the downhill direction of the surface in plan, whose upper edge follows the surface at a clearance, with&lt;br&gt;
contact features ("teeth") that approach or meet the surface at intervals, the plates standing on the build&lt;br&gt;
surface, optionally braced by a foot and widened at the base by a pad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.2 Finding where fins go.&lt;/strong&gt; Automatic detection of flat or near-flat downward faces by angle band, area and&lt;br&gt;
reach; grouping by normal tolerance; user selection by clicking a point on the surface, by painting, by drawing a&lt;br&gt;
line or region, or by selecting a face in CAD; selection of faces by curl risk (length of free edge, cooling time,&lt;br&gt;
distance from the bed, material); faces of any size, including faces normally considered printable; curved or&lt;br&gt;
faceted surfaces approximated piecewise; surfaces of multiple objects at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.3 Layout.&lt;/strong&gt; Fins along the downhill direction; at an angle to it; fanned from a point; in a grid or herringbone;&lt;br&gt;
crossing pairs (X); spacing fixed, scaled with overhang length, with angle, with face width, with material, or set&lt;br&gt;
so that the unsupported span between fins stays below a limit; denser near the low edge where curling starts;&lt;br&gt;
offset between neighbouring faces; joined across faces into one structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.4 Contact.&lt;/strong&gt; Teeth of any profile (trapezoid, triangle, rounded, stepped); points, ridges, pins, cones, fingers;&lt;br&gt;
teeth whose pitch varies along the fin (denser at the low end); anchor teeth that touch or overlap the part,&lt;br&gt;
anywhere along the fin and in any number, with any overlap; teeth that stop short at a gap of any size, fixed or&lt;br&gt;
proportional to the layer height; teeth printed with a different material, extruder, temperature, speed, flow or&lt;br&gt;
cooling; teeth with a breakaway neck or perforation; teeth that touch only in alternate layers; an interface layer,&lt;br&gt;
dissolvable or not, between teeth and part; contact made from the side rather than from below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.5 Profile.&lt;/strong&gt; A top edge that follows the surface at a relief; fins that stop at any fraction of the surface's&lt;br&gt;
height (the part bridging the rest), measured by height, by length along the slope or by the angle of the&lt;br&gt;
remaining span; a blunt top of any length or a minimum layer length, so that no layer of the support is shorter&lt;br&gt;
than a set length; a flat, rounded or stepped top; backs that are vertical, leaning at any angle, round, concave,&lt;br&gt;
stepped, or shaped to the minimum stable footprint; fins that start at a set height or reach down to the bed edge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.6 Section and structure.&lt;/strong&gt; Thickness constant, tapered, stepped or thickened at the base; solid, hollow,&lt;br&gt;
double-walled, corrugated, perforated, lattice-filled or sparse-infilled fins; fins with stiffening ribs or a&lt;br&gt;
flange along the top; fins printed as part of the model, as a separate object, as a slicer "support mesh", or&lt;br&gt;
directly as toolpaths at G-code level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.7 Feet and pads.&lt;/strong&gt; A crossbar of any length, height and position (under the tip, at the middle, at the low end,&lt;br&gt;
several); T, X, H or gusset-shaped feet; triangular gussets between fin and bed; a pad of any width and thickness,&lt;br&gt;
one or more layers, trimmed clear of the part, shared between neighbouring fins, or replaced by a raft or by mouse&lt;br&gt;
ears; tie bars linking fins to each other above the bed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.8 Workflow.&lt;/strong&gt; Generating fins in a slicer, a CAD program, a mesh editor, a plugin, a command line tool or a&lt;br&gt;
web service; replacing earlier fins when the same face is picked again; regenerating fins when the object is moved;&lt;br&gt;
removing all fins at once; previewing before adding; combining fins with tree or normal supports elsewhere on the&lt;br&gt;
same part; automatically turning off the slicer's own supports under the faces that have fins.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Test plan (please print this)
&lt;/h2&gt;

&lt;p&gt;Print the 35° test box (&lt;code&gt;data/tilted_box_35deg.stl&lt;/code&gt;, 150 × 40 × 25 mm) four ways, same filament, same settings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No support.&lt;/strong&gt; Photograph the base, measure curl at the low edge and mid-face with a feeler gauge or a ruler on
a flat surface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slicer supports&lt;/strong&gt; (normal, then tree), touching build plate only.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shark fins, defaults.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shark fins, anchors off&lt;/strong&gt; (&lt;code&gt;Anchor teeth&lt;/code&gt; 0).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For each, record: filament and time (slicer estimate and real), whether it finished, curl or sag of the base,&lt;br&gt;
time to remove the supports by hand, and the state of the base afterwards (photo). Then, if you are keen: tooth pitch&lt;br&gt;
3 vs 4 vs 6 mm; top cut 0, 15 and 25 %; a 45° box. Post your results as a GitHub issue or discussion with photos.&lt;br&gt;
Everyone who does gets credited in version 1.1.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Fin-ally, a social species
&lt;/h2&gt;

&lt;p&gt;Infant sharks do, do, do support prints well.&lt;/p&gt;

&lt;p&gt;(With apologies to a certain children's song, which is now going to be in your head until at least Thursday.)&lt;/p&gt;

&lt;p&gt;The serious point behind the joke: this method grew one review at a time. A fin that looked right in a diagram&lt;br&gt;
looked wrong in a slicer preview; a "more efficient" back edge turned out to cost more when measured. Each change&lt;br&gt;
came from someone looking at the output and saying "that bit's daft". The fastest way to find the next daft bit is&lt;br&gt;
more people looking. Print it, break it, measure it, and tell the rest of us. Makers, like fins, are stronger when&lt;br&gt;
they are spaced sensibly and braced against each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Code, data and how to reproduce
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The plugin&lt;/strong&gt; (Cura toolbar tool, menu, installer, command line tool, 22 geometry tests):
&lt;a href="https://github.com/The-Dorkknight/cura-shark-support" rel="noopener noreferrer"&gt;https://github.com/The-Dorkknight/cura-shark-support&lt;/a&gt;, version 0.2.1-wip (fin shapes as in 0.2.0).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In this repository:&lt;/strong&gt; &lt;code&gt;code/shark_core.py&lt;/code&gt; (the geometry, numpy only), &lt;code&gt;data/&lt;/code&gt; (the test boxes and their fins as
STL), &lt;code&gt;tools/make_figures.py&lt;/code&gt; (redraws Figures 1–4 and 9–11 from the code), &lt;code&gt;tools/gcode_side_view.py&lt;/code&gt; (draws
Figures 5–6 from any G-code), &lt;code&gt;tools/build_paper.py&lt;/code&gt; (builds the web page and PDF).&lt;/li&gt;
&lt;li&gt;Figures 5 and 6: PrusaSlicer 2.7.2, &lt;code&gt;--layer-height 0.2 --first-layer-height 0.2 --nozzle-diameter 0.4
--perimeters 2 --fill-density 15%&lt;/code&gt;, the box and its fins merged. Figures 7 and 8: Cura from source, run by the
plugin's own test driver under a virtual display.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  12. How this was made
&lt;/h2&gt;

&lt;p&gt;The geometry code, plugin, tests, measurements and literature search were done with an AI assistant (Claude,&lt;br&gt;
by Anthropic) working to my design intent, reviewed and steered by me at each step: the shark fin, the point&lt;br&gt;
contacts, the 4 mm brim, the crossbar, the triangle, the anchors, the blunt top and the toolbar button were all my&lt;br&gt;
calls; the assistant did the typing, the maths and the measuring, and owned up to its own mistakes listed in&lt;br&gt;
section 5. I think saying so plainly makes the work more trustworthy, not less.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Licence, and keeping it free
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text and figures:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/blob/main/paper/LICENSE.md" rel="noopener noreferrer"&gt;CC BY-SA 4.0&lt;/a&gt;. Share and adapt, credit me, keep it under the same licence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; PolyForm Noncommercial 1.0.0 (&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/blob/main/LICENSE-CODE.md" rel="noopener noreferrer"&gt;LICENSE-CODE.md&lt;/a&gt;). Free for hobbyists; commercial use
needs permission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The methods&lt;/strong&gt; in sections 3, 4 and 7 are dedicated to the public. This paper is a defensive publication: its
purpose is to be prior art, to make it much harder for anyone to patent these methods and restrict their use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  14. Disclaimers
&lt;/h2&gt;

&lt;p&gt;Not legal advice: what counts as prior art, and whether something infringes a patent, is decided by patent offices&lt;br&gt;
and courts. Not engineering certification: printed supports can fail, and parts can detach from the bed. Use at&lt;br&gt;
your own risk. Not affiliated with UltiMaker, Prusa Research, 3D Systems, Materialise or anyone else named here.&lt;/p&gt;

&lt;h2&gt;
  
  
  15. How to cite
&lt;/h2&gt;

&lt;p&gt;Armstrong, M. (2026). &lt;em&gt;Fin-ally Supported: Shark-fin supports for long, flat, tilted overhangs. A hobbyist's field&lt;br&gt;
guide and a defensive publication.&lt;/em&gt; Version 1.0, first published 19 October 2026.&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported" rel="noopener noreferrer"&gt;https://github.com/The-Dorkknight/fin-ally-supported&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prusa Research, "Support material", Prusa Knowledge Base.
&lt;a href="https://help.prusa3d.com/en/article/support-material_1698/" rel="noopener noreferrer"&gt;https://help.prusa3d.com/en/article/support-material_1698/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Printago, "How to Add Supports in PrusaSlicer (Including Organic and Custom)".
&lt;a href="https://printago.io/guides/prusaslicer-supports" rel="noopener noreferrer"&gt;https://printago.io/guides/prusaslicer-supports&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hubs, "Supports in 3D printing: a technology overview".
&lt;a href="https://www.hubs.com/knowledge-base/supports-3d-printing-technology-overview" rel="noopener noreferrer"&gt;https://www.hubs.com/knowledge-base/supports-3d-printing-technology-overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;T. Tricard, F. Claux, S. Lefebvre, "Ribbed Support Vaults for 3D Printing of Hollowed Objects",
&lt;em&gt;Computer Graphics Forum&lt;/em&gt;, 2019. &lt;a href="https://www.unilim.fr/pages_perso/frederic.claux/blog/2019/2019-06-21_Ribbed_support_vaults.html" rel="noopener noreferrer"&gt;https://www.unilim.fr/pages_perso/frederic.claux/blog/2019/2019-06-21_Ribbed_support_vaults.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Materialise, "How to create optimal support structures for metal 3D printing".
&lt;a href="https://www.materialise.com/en/inspiration/articles/optimal-metal-supports" rel="noopener noreferrer"&gt;https://www.materialise.com/en/inspiration/articles/optimal-metal-supports&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;3D Systems, Inc., US 9,469,057 B2, "Support structures and deposition techniques for 3D printing",
filed 17 May 2013, issued 18 October 2016. &lt;a href="https://patents.justia.com/patent/9469057" rel="noopener noreferrer"&gt;https://patents.justia.com/patent/9469057&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Patents entry for US9469057B2 (status as listed on 19 October 2026).
&lt;a href="https://patents.google.com/patent/US9469057B2/en" rel="noopener noreferrer"&gt;https://patents.google.com/patent/US9469057B2/en&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Software: The-Dorkknight/cura-shark-support, version 0.2.1-wip. &lt;a href="https://github.com/The-Dorkknight/cura-shark-support" rel="noopener noreferrer"&gt;https://github.com/The-Dorkknight/cura-shark-support&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About this repository
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/blob/main/paper/paper.md" rel="noopener noreferrer"&gt;&lt;code&gt;paper/paper.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The paper (this page is generated from it)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/blob/main/paper/fin-ally-supported-v1.0.pdf" rel="noopener noreferrer"&gt;&lt;code&gt;paper/fin-ally-supported-v1.0.pdf&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The paper as a PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/tree/main/paper/figures" rel="noopener noreferrer"&gt;&lt;code&gt;paper/figures/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;All figures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/blob/main/code/shark_core.py" rel="noopener noreferrer"&gt;&lt;code&gt;code/shark_core.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The fin geometry (Python, numpy only), as used by the plugin 0.2.0 and 0.2.1-wip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/tree/main/data" rel="noopener noreferrer"&gt;&lt;code&gt;data/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The test boxes and their fins as STL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/tree/main/tools" rel="noopener noreferrer"&gt;&lt;code&gt;tools/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Redraw the figures (&lt;code&gt;make_figures.py&lt;/code&gt;), plot G-code (&lt;code&gt;gcode_side_view.py&lt;/code&gt;), build the PDF and web page (&lt;code&gt;build_paper.py&lt;/code&gt;), write this page (&lt;code&gt;make_readme.py&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/tree/main/docs" rel="noopener noreferrer"&gt;&lt;code&gt;docs/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The web version for GitHub Pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To rebuild everything: &lt;code&gt;python3 tools/make_figures.py&lt;/code&gt; (numpy, matplotlib, Pillow), then&lt;br&gt;
&lt;code&gt;python3 tools/build_paper.py&lt;/code&gt; (pandoc, Chromium), then &lt;code&gt;python3 tools/make_readme.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;GitHub's &lt;strong&gt;Cite this repository&lt;/strong&gt; button uses &lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/blob/main/CITATION.cff" rel="noopener noreferrer"&gt;&lt;code&gt;CITATION.cff&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Join in:&lt;/strong&gt; print the test plan (section 8), post results with photos as an issue or discussion, fork it, port it to&lt;br&gt;
another slicer, or point out prior art I missed. Everyone who contributes gets credited in the next version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Licence:&lt;/strong&gt; paper text and figures &lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/blob/main/LICENSE.md" rel="noopener noreferrer"&gt;CC BY-SA 4.0&lt;/a&gt;; code in &lt;code&gt;code/&lt;/code&gt; and &lt;code&gt;tools/&lt;/code&gt;&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported/blob/main/LICENSE-CODE.md" rel="noopener noreferrer"&gt;PolyForm Noncommercial 1.0.0&lt;/a&gt; (source-available, not open source); the methods described are&lt;br&gt;
dedicated to the public as a defensive publication. Not legal advice.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Shark Support for UltiMaker Cura</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Tue, 20 Oct 2026 13:42:30 +0000</pubDate>
      <link>https://dev.to/open-madscience/shark-support-for-ultimaker-cura-5ep0</link>
      <guid>https://dev.to/open-madscience/shark-support-for-ultimaker-cura-5ep0</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;WORK IN PROGRESS: MANY FEATURES MAY BE BROKEN&lt;/strong&gt; (proof of concept, 0.2.1-wip)
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FThe-Dorkknight%2Fcura-shark-support%2Fmain%2Fdocs%2Fimages%2Ficon-512.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FThe-Dorkknight%2Fcura-shark-support%2Fmain%2Fdocs%2Fimages%2Ficon-512.png" alt="Shark Support icon" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source-available · Non-commercial.&lt;/strong&gt; Copyright Matthew Armstrong. Licensed under the&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/cura-shark-support/blob/main/LICENSE.md" rel="noopener noreferrer"&gt;PolyForm Noncommercial License 1.0.0&lt;/a&gt;. No warranty, no liability: use at your own risk.&lt;/p&gt;

&lt;p&gt;Shark Support adds thin &lt;strong&gt;shark-fin supports&lt;/strong&gt; under big, flat, tilted faces. The typical case: you print a long&lt;br&gt;
box tilted onto one long edge so it is strong along its length. Its base becomes a long 30–55° overhang that is&lt;br&gt;
fine in theory but sags or curls over that length. Shark Support puts a row of fins under it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each fin is a thin vertical plate that follows the slope of the part, &lt;strong&gt;stops at 85 % of its height&lt;/strong&gt; (the part
bridges the last bit), has a &lt;strong&gt;blunt 5 mm flat top&lt;/strong&gt; (no tiny, slow top layers) and a &lt;strong&gt;straight back&lt;/strong&gt; down to
the bed&lt;/li&gt;
&lt;li&gt;it &lt;strong&gt;only touches the part at small teeth&lt;/strong&gt;, a dotted line of points every 3 mm that snaps off cleanly, starting
right at the fin's low end. The &lt;strong&gt;first two teeth really touch&lt;/strong&gt; (0.1 mm into the part) to hold the start of the
overhang; the rest stop 0.15 mm short&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;crossbar foot&lt;/strong&gt; under the tip (¼ of the fin's length, 4 mm high) and a &lt;strong&gt;4 mm one-layer brim pad&lt;/strong&gt; keep tall
fins standing&lt;/li&gt;
&lt;li&gt;in Cura it is a &lt;strong&gt;button in the left toolbar&lt;/strong&gt;: &lt;strong&gt;pick the face&lt;/strong&gt; you want supported with the mouse, or press
&lt;strong&gt;Auto&lt;/strong&gt; for every big tilted flat face, and &lt;strong&gt;Remove all&lt;/strong&gt; to clear them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyz29kuixhehkkl4lwz33.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyz29kuixhehkkl4lwz33.webp" alt="Shark fins under a tilted box in Cura" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Real screenshot: the plugin running inside Cura (5.15 development source, see Testing), with&lt;br&gt;
fins under the 35° test box and the Shark Support panel open from its toolbar button.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjuoijy4wbox76ns5iqu7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjuoijy4wbox76ns5iqu7.webp" alt="One fin, side view" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Use it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Load your model and &lt;strong&gt;tilt it the way you will print it&lt;/strong&gt; (Rotate tool, &lt;em&gt;Lay flat&lt;/em&gt;, etc.).&lt;/li&gt;
&lt;li&gt;Select it, then click the &lt;strong&gt;shark-fin button&lt;/strong&gt; at the bottom of the left toolbar (under move, scale, rotate,
mirror, per-model settings and support blocker). Its panel opens:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FThe-Dorkknight%2Fcura-shark-support%2Fmain%2Fdocs%2Fimages%2Ftoolbar-panel.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FThe-Dorkknight%2Fcura-shark-support%2Fmain%2Fdocs%2Fimages%2Ftoolbar-panel.webp" alt="Shark Support toolbar button and panel" width="400" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pick face&lt;/strong&gt;, then &lt;strong&gt;click the underside face&lt;/strong&gt; that needs support. Fins appear under that one face. Pick
 more faces the same way; picking a face again replaces its fins (handy after changing settings). Press the
 button again to stop picking. Turn the view so you can see the underside (a low side view works).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto&lt;/strong&gt;: fins under every big tilted flat face of the selected model(s), replacing their earlier fins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove all&lt;/strong&gt;: removes every Shark fin object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings...&lt;/strong&gt;: the numbers below.

&lt;ol&gt;
&lt;li&gt;A message says how many fins were made. New objects called &lt;em&gt;Shark fins (your model...)&lt;/em&gt; appear.&lt;/li&gt;
&lt;li&gt;Slice and print as usual. Normal support for that model is switched &lt;strong&gt;off&lt;/strong&gt; (a per-model setting) so Cura does
not add its own supports under the same face. You can change that in the settings.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same actions are in the menu &lt;strong&gt;Extensions &amp;gt; Shark Support&lt;/strong&gt;. If you move or rotate the model afterwards, use&lt;br&gt;
&lt;strong&gt;Remove all&lt;/strong&gt; and add the fins again: they are a separate object and do not follow the model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9uouisy3zt4vttr25aww.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9uouisy3zt4vttr25aww.webp" alt="Picking a face with the mouse" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Settings (Settings... in the panel, or Extensions &amp;gt; Shark Support &amp;gt; Settings...)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7zrpygciynt7o632cugb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7zrpygciynt7o632cugb.webp" alt="Settings dialog" width="780" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Overhang angle from / to&lt;/td&gt;
&lt;td&gt;20° / 70°&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Auto&lt;/em&gt; puts fins under faces in this range (Cura's angles: 0° = wall, 90° = ceiling). A face you pick gets fins whatever its angle, as long as it faces down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smallest face / Shortest slope&lt;/td&gt;
&lt;td&gt;300 mm² / 15 mm&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Auto&lt;/em&gt; leaves smaller faces alone (picked faces ignore this)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fin spacing / Side margin&lt;/td&gt;
&lt;td&gt;30 mm / 4 mm&lt;/td&gt;
&lt;td&gt;Distance between fins across the face, and from its sides&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start height&lt;/td&gt;
&lt;td&gt;4 mm&lt;/td&gt;
&lt;td&gt;Fins start where the face is this high (low down it needs no help)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thickness at bed / at top&lt;/td&gt;
&lt;td&gt;2.4 / 1.2 mm&lt;/td&gt;
&lt;td&gt;Fins taper; with a 0.4 mm nozzle that is 6 lines at the bed, 3 at the top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gap between teeth&lt;/td&gt;
&lt;td&gt;1 mm&lt;/td&gt;
&lt;td&gt;The fin edge stays this far under the part except at the teeth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tooth spacing / tip length&lt;/td&gt;
&lt;td&gt;3 / 0.6 mm&lt;/td&gt;
&lt;td&gt;The contact points. The first tooth starts at the fin's low end, the last sits at the tall end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tip gap&lt;/td&gt;
&lt;td&gt;0.15 mm&lt;/td&gt;
&lt;td&gt;All teeth except the anchors stop this far under the part. Smaller = stronger contact, harder removal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anchor teeth / overlap&lt;/td&gt;
&lt;td&gt;2 / 0.1 mm&lt;/td&gt;
&lt;td&gt;The first teeth from the low end reach this far &lt;em&gt;into&lt;/em&gt; the part so they bond slightly and hold the start of the overhang. 0 turns it off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top left free&lt;/td&gt;
&lt;td&gt;15 %&lt;/td&gt;
&lt;td&gt;The fin stops this far below the top of the face; the part bridges the rest. 0 runs it to the top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blunt tip length&lt;/td&gt;
&lt;td&gt;5 mm&lt;/td&gt;
&lt;td&gt;The fin's flat top. No layer of the fin is shorter than this, so the printer never slows down for tiny top moves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Back lean&lt;/td&gt;
&lt;td&gt;20°&lt;/td&gt;
&lt;td&gt;Only with &lt;em&gt;Lean the back out&lt;/em&gt; on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crossbar length / height&lt;/td&gt;
&lt;td&gt;0.25 × fin / 4 mm&lt;/td&gt;
&lt;td&gt;The foot across the fin under its tip (a second one in the middle for fins over 100 mm)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brim width / thickness&lt;/td&gt;
&lt;td&gt;4 mm / first layer&lt;/td&gt;
&lt;td&gt;One-layer pad around each fin and crossbar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lean the back out to the bed&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;The back slopes out 20° instead of going straight down: wider base, but more plastic and time (test box: 1.97 m and 42 min instead of 1.68 m and 40 min). The command line also offers round and concave backs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Print fins as a Cura support mesh&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;Experimental: Cura then prints them with your support pattern and density&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Extensions &amp;gt; Shark Support &amp;gt; Run self-check&lt;/strong&gt; builds a test box in memory, runs the plugin's own code against it&lt;br&gt;
(including face picking and the toolbar button),&lt;br&gt;
and shows PASS/FAIL per check (also saved to &lt;code&gt;shark_support_selfcheck.txt&lt;/code&gt; in your temp folder). Run it once after&lt;br&gt;
installing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Without Cura (PrusaSlicer, OrcaSlicer, anything)
&lt;/h2&gt;

&lt;p&gt;The geometry code needs only numpy, so there is a command line tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 shark_cli.py part.stl                   # writes part_fins.stl
python3 shark_cli.py part.stl --spacing 25 --merged part_with_fins.stl
python3 shark_cli.py part.stl --back slope       # back leaning out instead of straight down
python3 shark_cli.py --help                     # every setting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The STL must already be oriented for printing (Z up, sitting on the bed). Import the fins STL as a second part at&lt;br&gt;
the same position (PrusaSlicer: &lt;em&gt;Add part&lt;/em&gt;; or slice the &lt;code&gt;--merged&lt;/code&gt; file). The repository has examples in&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/cura-shark-support/tree/main/examples" rel="noopener noreferrer"&gt;&lt;code&gt;examples/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9z4dooykdjlfmpfzc3xc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9z4dooykdjlfmpfzc3xc.webp" alt="Real G-code from PrusaSlicer, sliced through one fin" width="668" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Real G-code: the merged test box sliced by PrusaSlicer 2.7.2 on the command line, showing every extrusion within&lt;br&gt;
0.5 mm of the middle fin. The fin is solid, touches the part only at the teeth, stops below the top of the face&lt;br&gt;
with a flat top, and goes straight down at the back.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What exists and what does not
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exists (0.2.1-wip)&lt;/th&gt;
&lt;th&gt;Does not exist yet&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fins under flat tilted faces: picked with the mouse, or found automatically&lt;/td&gt;
&lt;td&gt;Fins under curved or organic surfaces (it needs flat faces)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fins cut at 85 %, blunt flat top, straight back, teeth every 3 mm, 2 anchor teeth, crossbar, brim pad&lt;/td&gt;
&lt;td&gt;Painting exactly where on a face fins go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cura toolbar button with panel, menu, settings dialog, self-check, undo&lt;/td&gt;
&lt;td&gt;Fins that follow the model when it is moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installer that checks Cura's log; &lt;code&gt;.curapackage&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Live preview while editing settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Command line tool for any slicer&lt;/td&gt;
&lt;td&gt;PrusaSlicer / OrcaSlicer plugin (they have no Python plugin API)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Experimental "Cura support mesh" mode&lt;/td&gt;
&lt;td&gt;A real print test (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What was tested
&lt;/h2&gt;

&lt;p&gt;Be aware of exactly what has and has not been tried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Geometry:&lt;/strong&gt; 22 automatic tests (&lt;code&gt;python3 tests/test_core.py&lt;/code&gt;): closed outward meshes, fins never reach into the
part (only the anchor teeth reach in, exactly 0.1 mm; with anchors off every tip is 0.15 mm below it), boxes tilted 35° and 45°, turned on the bed, standing on a corner,
the 85 % cut and 5 mm flat top, picking a face (and refusing a top face or a miss), triangle-soup input, spacing, long fins, no fins on a plain cube. A 196,000-triangle mesh took about 2 s on the
development machine (not timed on a 7th-gen Core i3).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inside Cura:&lt;/strong&gt; Cura could not be downloaded in the build sandbox, so it was run &lt;strong&gt;from Cura's and Uranium's
source (5.15 development branch, plugin SDK 8.13)&lt;/strong&gt; under a virtual display, with stand-ins only for Cura's C++
parts. There the plugin loaded, its &lt;strong&gt;toolbar button&lt;/strong&gt; appeared and opened its panel, &lt;strong&gt;a real mouse click
(sent with xdotool) on the box's underside made fins for that face&lt;/strong&gt;, picking it again replaced them, &lt;em&gt;Auto&lt;/em&gt;
replaced those with automatic fins with the right per-object settings, the menu and dialog opened, the self-check passed, support-mesh mode and &lt;em&gt;Remove all fins&lt;/em&gt; worked, and Cura's package
manager listed it as installed, both after the installer and after Cura's own &lt;code&gt;.curapackage&lt;/code&gt; route. &lt;strong&gt;Slicing in
Cura was not tested&lt;/strong&gt; (the slicing engine is one of the C++ parts that could not run).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slicing:&lt;/strong&gt; the fins were sliced for real with &lt;strong&gt;PrusaSlicer 2.7.2&lt;/strong&gt; (command line), picture above. For the
test box the fins take 1.68 m of filament and about 40 minutes on their own (PrusaSlicer defaults, 0.2 mm
layers); the 0.1.2 fins, which ran to the top with a sharp tip, took 1.74 m and about 43 minutes. The zoom below shows the anchor teeth meeting the part's outer wall in the G-code while the other
teeth leave a gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpod23p0gy3d529prpz2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpod23p0gy3d529prpz2.webp" alt="G-code zoom on the anchor teeth" width="787" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not tested:&lt;/strong&gt; a real print, Cura 5.0–5.10 releases (face picking uses a different path there, written to
match Cura 5.0's own support blocker but not run), Windows and Mac (launchers and installer), Flatpak Cura.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Research paper
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/The-Dorkknight/cura-shark-support/blob/main/paper/paper.md" rel="noopener noreferrer"&gt;Fin-ally Supported: Shark-fin supports for long, flat, tilted overhangs&lt;/a&gt;&lt;/strong&gt; (version 1.0, first&lt;br&gt;
published 19 October 2026) · &lt;a href="https://github.com/The-Dorkknight/cura-shark-support/blob/main/paper/fin-ally-supported-v1.0.pdf" rel="noopener noreferrer"&gt;PDF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything behind this plugin written up: the problem, what already exists (including a live patent on "fine point"&lt;br&gt;
supports and how this differs), the method in enough detail to rebuild it, every mistake made on the way, the&lt;br&gt;
numbers, a test plan, and a defensive disclosure of the method and its variations, dedicated to the public so&lt;br&gt;
nobody can patent them. It is published on its own at&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported" rel="noopener noreferrer"&gt;The-Dorkknight/fin-ally-supported&lt;/a&gt;; &lt;code&gt;paper/&lt;/code&gt; is a copy that&lt;br&gt;
travels with the plugin. Text and figures: CC BY-SA 4.0. Infant sharks do, do, do support prints well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to cite:&lt;/strong&gt; Armstrong, M. (2026). &lt;em&gt;Fin-ally Supported: Shark-fin supports for long, flat, tilted overhangs. A&lt;br&gt;
hobbyist's field guide and a defensive publication.&lt;/em&gt; Version 1.0, 19 October 2026.&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/fin-ally-supported" rel="noopener noreferrer"&gt;https://github.com/The-Dorkknight/fin-ally-supported&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/cura-shark-support/blob/main/HOW_IT_WORKS.md" rel="noopener noreferrer"&gt;HOW_IT_WORKS.md&lt;/a&gt;: what the plugin reads, changes and never touches&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/cura-shark-support/blob/main/DESIGN_NOTES.md" rel="noopener noreferrer"&gt;DESIGN_NOTES.md&lt;/a&gt;: research notes, decisions, measurements, ideas&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/cura-shark-support/tree/main/paper" rel="noopener noreferrer"&gt;paper/&lt;/a&gt;: the research paper (Markdown and PDF)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/cura-shark-support/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/cura-shark-support/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/cura-shark-support/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/cura-shark-support/blob/main/THIRD_PARTY.md" rel="noopener noreferrer"&gt;THIRD_PARTY.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not affiliated with or endorsed by UltiMaker. "Cura" and "UltiMaker" are names of their owners.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/cura-shark-support" rel="noopener noreferrer"&gt;The-Dorkknight/cura-shark-support on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>software</category>
      <category>softwaredevelopment</category>
      <category>tools</category>
    </item>
    <item>
      <title>FREE (open) Lizard Scanner</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Mon, 19 Oct 2026 20:45:24 +0000</pubDate>
      <link>https://dev.to/open-madscience/free-open-lizard-scanner-503c</link>
      <guid>https://dev.to/open-madscience/free-open-lizard-scanner-503c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgewdnnbxsqyadosnstxb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgewdnnbxsqyadosnstxb.jpg" alt="A lizard revolutionary waving a " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Paused for a month or two.&lt;/strong&gt; Calibration turned out to be more boring than cleaning, and after weeks of&lt;br&gt;
mad science projects the cleaning has been neglected for long enough. The app works as far as described&lt;br&gt;
below; the notes say exactly where it stopped. Issues and ideas are still welcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The paper:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/docs/paper/down-the-lizard-hole.pdf" rel="noopener noreferrer"&gt;Down the Lizard Hole&lt;/a&gt;, the whole reverse-engineering&lt;br&gt;
story in plain English (also in its own repository,&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/down-the-lizard-hole" rel="noopener noreferrer"&gt;down-the-lizard-hole&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WORK IN PROGRESS: MANY FEATURES MAY BE BROKEN.&lt;/strong&gt; Version 0.5.2-wip. Proof of concept.&lt;br&gt;
The camera control, the light buttons and the diagnostics have run on the owner's real CR-Scan&lt;br&gt;
Lizard. The first real wall test (0.4) failed because the projector throws &lt;strong&gt;stripes, not dots&lt;/strong&gt;;&lt;br&gt;
0.5 rebuilds depth for the stripes and was checked on that wall test's saved data and one saved&lt;br&gt;
picture (walls back within 2 mm). The first live test (19 October) gave a flat wall where the&lt;br&gt;
calibration was good, with wrong-stripe streaks that 0.5.1 removes; a second calibration did worse for&lt;br&gt;
reasons not yet known. See &lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/docs/research/real-scanner/2026-10-18-stripes.md" rel="noopener noreferrer"&gt;the findings&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Licence:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/LICENSE.md" rel="noopener noreferrer"&gt;PolyForm Noncommercial 1.0.0&lt;/a&gt;. Source-available, non-commercial.&lt;br&gt;
Free for personal and other non-commercial use; commercial use needs permission. No warranty&lt;br&gt;
and no liability: it is provided as is. Not made by, or affiliated with, Creality.&lt;/p&gt;

&lt;p&gt;A standalone app for the &lt;strong&gt;Creality CR-Scan Lizard&lt;/strong&gt; 3D scanner that does not need Creality's&lt;br&gt;
software: a live infrared picture, &lt;strong&gt;automagic buttons instead of sliders&lt;/strong&gt; for the light, a&lt;br&gt;
&lt;strong&gt;wall test&lt;/strong&gt; that gives the scanner a rough depth calibration, a live depth view, point-cloud&lt;br&gt;
export and recordings. It is built on the bones of&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/scantastic" rel="noopener noreferrer"&gt;Scantastic&lt;/a&gt; and the Lizard engine from&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/who-are-yousb" rel="noopener noreferrer"&gt;Who are yoUSB?&lt;/a&gt;, and is laid out so the Lizard&lt;br&gt;
can move into Scantastic later (&lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/INTEGRATION.md" rel="noopener noreferrer"&gt;INTEGRATION.md&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpi4q20guoa3z9m7kvju0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpi4q20guoa3z9m7kvju0.webp" alt="Lizard Scanner showing the depth view of a ball in front of a wall" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Screenshots are of the built-in simulated scanner, not a real Lizard.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What exists, and what does not
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Works (on the simulated scanner)&lt;/th&gt;
&lt;th&gt;Not yet&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Finding the Lizard's two cameras (USB bcdc:000a / 000b) and starting them (Linux)&lt;/td&gt;
&lt;td&gt;Tried on a real Lizard with this app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live picture, "pattern only" view, camera A + B side by side&lt;/td&gt;
&lt;td&gt;Full 3D scans (many views joined into a mesh): that is Scantastic's job, see &lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/INTEGRATION.md" rel="noopener noreferrer"&gt;INTEGRATION.md&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AUTO-TUNE, three presets, Brighter / Dimmer, flash capped at 1 ms&lt;/td&gt;
&lt;td&gt;Lens distortion correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diagnostics: 10 checks and a report zip&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wall test: rough depth calibration from 3 to 5 walls, saved as you go, all in one folder, one-click zip&lt;/td&gt;
&lt;td&gt;Creality's own factory calibration (not found)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live depth view, save points as .ply&lt;/td&gt;
&lt;td&gt;Windows and macOS camera paths (written, never run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recording sessions and playing them back&lt;/td&gt;
&lt;td&gt;Colour (the Lizard's pictures are infrared)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Light test, "What is camera B?" test&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The picture
&lt;/h3&gt;

&lt;p&gt;Plug the Lizard in. On Linux the picture starts by itself within a few seconds (the app&lt;br&gt;
looks for it every 2.5 s). The green tag on the picture says how many pictures a second&lt;br&gt;
arrive and how much of the view shows the projector's stripes. The projector (camera A's light)&lt;br&gt;
flashes once per picture, so every picture carries its pattern; the red glow looks like flicker.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4cdkve0ovggv8rkirg4f.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4cdkve0ovggv8rkirg4f.webp" alt="The live infrared picture with the light buttons" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;View&lt;/strong&gt; row switches between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Picture&lt;/strong&gt;: the infrared picture with the projector's stripes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pattern only&lt;/strong&gt;: the stripes with the background light taken away. What the depth is made from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depth&lt;/strong&gt;: needs a wall test first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A + B&lt;/strong&gt;: both cameras side by side.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The light: automagic buttons
&lt;/h3&gt;

&lt;p&gt;Creality's own app only ever changes two settings, exposure and gain. Here they are a&lt;br&gt;
ladder of 11 light levels instead of two sliders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AUTO-TUNE&lt;/strong&gt; walks the ladder until the stripes are bright but not blown out, from whatever
is in front of the scanner. Press it again to stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shiny / white&lt;/strong&gt;, &lt;strong&gt;Normal&lt;/strong&gt;, &lt;strong&gt;Dark / black&lt;/strong&gt;: presets. Normal is what Creality's app
uses (1 ms, gain 65); Shiny / white is its dimmest setting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dimmer / Brighter&lt;/strong&gt;: one step of the ladder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The ring's brightness is the flash length.&lt;/strong&gt; The white ring is camera B's light, the infrared&lt;br&gt;
projector camera A's. Both cameras start at Creality's 1 ms, never on their own automatic exposure,&lt;br&gt;
and camera B follows these buttons whenever it runs. The ring and the projector flash for as&lt;br&gt;
long as the camera exposes, and at Creality's 1 ms the ring is already very bright. So the&lt;br&gt;
ladder never flashes longer than 1 ms; above that it adds gain (picture amplification), which&lt;br&gt;
brightens the picture without touching the lights. The bar under the buttons shows the rungs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Allow longer flashes&lt;/strong&gt;: an advanced switch, with a warning, for dark objects. Brighter then
goes up to 6 ms (the camera's own starting value, never more); those rungs show red. It
switches itself off when the app closes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camera's own settings (long flashes)&lt;/strong&gt;: back to the camera's own automatic exposure. Only
with longer flashes allowed, because the camera then picks its own (often long) flash.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every raw setting is still there under &lt;em&gt;Every camera setting (advanced)&lt;/em&gt;; setting a flash longer&lt;br&gt;
than 1 ms there asks first.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The wall test: rough depth calibration
&lt;/h3&gt;

&lt;p&gt;The Lizard's camera A sees a fixed pattern from its projector: dense vertical stripes, each a string&lt;br&gt;
of beads whose width wobbles. When the scanner moves closer to a wall, the stripes slide sideways in&lt;br&gt;
the picture, by an amount that depends on the distance (and, on the real scanner, a little on where&lt;br&gt;
in the picture). The beads' wobble tells one stripe from the next. Measure that slide at a few known&lt;br&gt;
distances and every picture afterwards can be turned into depth. The original Kinect worked the same&lt;br&gt;
way with dots.&lt;/p&gt;

&lt;p&gt;You need a flat, plain, matt wall and a tape measure. About five minutes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;WALL TEST&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Point the scanner straight at the wall. Measure to the wall from one fixed point on the scanner
(the front glass is easiest; any point works if you use the same one every time; depth is then
counted from that point) and type the distance in millimetres.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;RECORD THIS WALL&lt;/strong&gt; and hold still for two seconds. (Before the first wall the app
runs AUTO-TUNE by itself.)&lt;/li&gt;
&lt;li&gt;Repeat at five distances: 18, 23, 28, 33 and 38 cm are suggested (Creality gives the
Lizard's working range as 15 to 40 cm). Three is the least; spreading the walls over the
whole range matters, because depth gets less certain beyond the nearest and farthest wall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing to press to save.&lt;/strong&gt; Every wall is saved the moment it is recorded, and from the third
wall on the calibration is fitted and saved by itself; the Depth view turns on. The list shows
how well each wall fits, and the window warns about a wall at an angle (one that disagrees with
the others is left out) or a scanner turned to the wall. &lt;em&gt;FIT AND SAVE AGAIN&lt;/em&gt; is only needed after
typing a picture width.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Everything for one scanner's calibration is in one folder&lt;/strong&gt;, &lt;code&gt;calibration/&amp;lt;serial&amp;gt;/&lt;/code&gt;: the fit&lt;br&gt;
(&lt;code&gt;calibration.json&lt;/code&gt;), the wall patterns and K map (&lt;code&gt;reference.npz&lt;/code&gt;), the measured shift maps, the report,&lt;br&gt;
and &lt;code&gt;walls/&lt;/code&gt; with three of the camera's own full-size pictures of each wall. &lt;strong&gt;ZIP CALIBRATION&lt;/strong&gt; (in the&lt;br&gt;
wall test, or &lt;em&gt;Zip this scanner's calibration&lt;/em&gt; under Depth) packs it into one file next to it, with the&lt;br&gt;
serial hidden when the Capture box says so. &lt;em&gt;Load saved walls&lt;/em&gt; brings the walls back to fit again, also&lt;br&gt;
from an older version of the app: a 0.4 calibration is refitted for the stripes without new walls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1wysaonfxitedkya9yhn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1wysaonfxitedkya9yhn.webp" alt="The wall test after four walls" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Optional: tick &lt;em&gt;I measured the picture's width&lt;/em&gt; and type how wide the picture is on the wall&lt;br&gt;
at a known distance. Without it, the lens is guessed from Creality's spec (200 mm across),&lt;br&gt;
which only affects sideways sizes in exported points, not depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Depth and points
&lt;/h3&gt;

&lt;p&gt;The Depth view colours the scene green in the sweet spot (18 to 35 cm), through yellow to red&lt;br&gt;
outside it, with shading so shapes show. &lt;strong&gt;Save this view as points (.ply)&lt;/strong&gt; writes the current&lt;br&gt;
depth as a point cloud in millimetres, which MeshLab, CloudCompare or Blender can open.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Capturing frames, recording and playing back
&lt;/h3&gt;

&lt;p&gt;Every saved picture is stamped with &lt;strong&gt;when it arrived, which camera, the exposure, the gain, the&lt;br&gt;
auto-exposure mode and every other camera setting in force&lt;/strong&gt;, plus its brightness, fine detail, how much&lt;br&gt;
of it shows the projector's pattern, its sharpness, the light-ladder rung and the stream's own picture&lt;br&gt;
number. Every setting change (light buttons, AUTO-TUNE, tests) is logged as it happens, so pictures from&lt;br&gt;
before and after a change never get mixed up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Save frame now&lt;/strong&gt; (Capture panel): one picture from each running camera.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RECORD&lt;/strong&gt;: every picture from every running camera (camera B joins when the A + B view starts it).
&lt;em&gt;every&lt;/em&gt; saves only every Nth picture for long recordings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notes&lt;/strong&gt;: type e.g. "covered camera A" and press Add note; it is stamped on the next picture and kept in
the timeline. With nothing recording, it goes on the next capture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hide the scanner's serial&lt;/strong&gt; (on by default) masks it in captures and in test reports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each capture is a folder &lt;code&gt;captures/capture_&amp;lt;date&amp;gt;_&amp;lt;time&amp;gt;_&amp;lt;name&amp;gt;/&lt;/code&gt;: the camera's own JPEGs (no quality lost)&lt;br&gt;
in &lt;code&gt;frames/&lt;/code&gt;, &lt;code&gt;frames.csv&lt;/code&gt; (one row per picture with all the stamps), &lt;code&gt;events.jsonl&lt;/code&gt; (settings snapshots,&lt;br&gt;
every change, notes) and &lt;code&gt;session.json&lt;/code&gt;. &lt;em&gt;Source &amp;gt; Play back a recording...&lt;/em&gt; plays one back as if it were&lt;br&gt;
the scanner. The capture code is shared with Who are yoUSB? (&lt;code&gt;lizard_framecap.py&lt;/code&gt;); see&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/docs/framecap-in-lizard-scanner.md" rel="noopener noreferrer"&gt;docs/framecap-in-lizard-scanner.md&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Diagnostics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Diagnostics&lt;/strong&gt; opens one window with every check. Point the scanner at a plain wall 20 to 30 cm&lt;br&gt;
away and press &lt;strong&gt;RUN QUICK CHECKS&lt;/strong&gt; (about 10 seconds):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;USB connection&lt;/td&gt;
&lt;td&gt;Both cameras there, at USB 2 speed, on the right driver, not behind an extra hub (Linux)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camera A video&lt;/td&gt;
&lt;td&gt;Pictures a second, late pictures, whether the stripe pattern is in every picture (or the lights pulse), picture sizes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video modes and settings&lt;/td&gt;
&lt;td&gt;Every mode and setting both cameras offer (saved in the report)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serial number&lt;/td&gt;
&lt;td&gt;The scanner's serial, read the way Creality's app does&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Projector and room light&lt;/td&gt;
&lt;td&gt;How much of the view shows the stripe pattern, how strong it is, how much infrared is in the room (judged between the stripes; sunlight swamps them), anything blown out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Depth speed&lt;/td&gt;
&lt;td&gt;How long one depth picture takes on this computer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;and, one at a time:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sensor map&lt;/td&gt;
&lt;td&gt;Runs camera A alone, B alone, then both, 3 s each; measures each camera's rate, stripe pattern, colour and pulse, and asks which light you saw each time. Answers "which camera does what"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What is camera B?&lt;/td&gt;
&lt;td&gt;Streams camera B alone (at a 1 ms flash): colour or grey, does its light pulse. On the owner's Lizard it is the camera with the white ring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flat wall check&lt;/td&gt;
&lt;td&gt;After the wall test: how flat a wall comes out, how far away, and (with a tape-measured distance) how far off. Also spots lens bending at the edges. The best test of a calibration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Light test&lt;/td&gt;
&lt;td&gt;Steps the flash from 0.1 ms to 1 ms while you watch the ring, then asks what you saw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gain test&lt;/td&gt;
&lt;td&gt;Holds the flash at 1 ms and steps the gain: if the ring changes, the firmware ties the LEDs to gain too&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Save diagnostics report (.zip)&lt;/strong&gt; puts the results, the settings dumps, sample pictures, the&lt;br&gt;
calibration report and the light / gain test answers in one zip in &lt;code&gt;reports/&lt;/code&gt;, with the serial&lt;br&gt;
hidden if you like: ready to attach to a GitHub issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpsrb0abq5s35x3j38pfw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpsrb0abq5s35x3j38pfw.webp" alt="The Diagnostics window after the quick checks" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking for Creality's own calibration? See&lt;br&gt;
&lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/docs/research/calibration-hunt.md" rel="noopener noreferrer"&gt;docs/research/calibration-hunt.md&lt;/a&gt; and the read-only Windows&lt;br&gt;
script &lt;code&gt;tools/Find-CR-Scan-Calibration.bat&lt;/code&gt; (untested).&lt;/p&gt;

&lt;h3&gt;
  
  
  More screenshots
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdl4p5a8vqhjly43089ve.webp" alt="Scanner off: TV static until the Lizard is plugged in" width="800" height="529"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl79gdtdqpxzl3msdl4pa.webp" alt="Pattern only: the projector's stripes with the background light removed" width="800" height="529"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanner off: static until the Lizard is plugged in&lt;/td&gt;
&lt;td&gt;Pattern only: what depth is made from&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvlziu37cjfphxp95nh31.webp" alt="The plain look" width="800" height="529"&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The plain look (&lt;code&gt;--plain&lt;/code&gt;, or Look: Plain)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/HOW_IT_WORKS.md" rel="noopener noreferrer"&gt;HOW_IT_WORKS.md&lt;/a&gt;: what the app reads, changes and never does; the maths.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/DESIGN_NOTES.md" rel="noopener noreferrer"&gt;DESIGN_NOTES.md&lt;/a&gt;: findings, decisions, measurements, what is untested, what next.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/INTEGRATION.md" rel="noopener noreferrer"&gt;INTEGRATION.md&lt;/a&gt;: putting the Lizard into Scantastic.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/docs/research/README.md" rel="noopener noreferrer"&gt;docs/research&lt;/a&gt;: the USB research behind the camera control.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;, &lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt;, &lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt;,
&lt;a href="https://github.com/The-Dorkknight/lizard-scanner/blob/main/THIRD_PARTY.md" rel="noopener noreferrer"&gt;THIRD_PARTY.md&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Required Notice: Copyright Matthew Armstrong (&lt;a href="https://github.com/The-Dorkknight" rel="noopener noreferrer"&gt;https://github.com/The-Dorkknight&lt;/a&gt;)&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/lizard-scanner" rel="noopener noreferrer"&gt;The-Dorkknight/lizard-scanner on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>hardware</category>
      <category>opensource</category>
      <category>tools</category>
    </item>
    <item>
      <title>SimpleCAD</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Mon, 19 Oct 2026 20:33:23 +0000</pubDate>
      <link>https://dev.to/open-madscience/simplecad-2kde</link>
      <guid>https://dev.to/open-madscience/simplecad-2kde</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F60db8rjun2564t1qzhiq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F60db8rjun2564t1qzhiq.png" alt="SimpleCAD in FreeCAD 1.1.4 on a 1366 px laptop screen" width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source-available · Non-commercial&lt;/strong&gt; · Licensed under the &lt;a href="https://github.com/The-Dorkknight/simplecad/blob/main/LICENSE.md" rel="noopener noreferrer"&gt;PolyForm Noncommercial License 1.0.0&lt;/a&gt; · Copyright Matthew Armstrong · &lt;strong&gt;No warranty, no liability: use at your own risk.&lt;/strong&gt; Free for personal, hobby, study and other non-commercial use; commercial use needs my separate permission. Not legal advice. See &lt;a href="https://github.com/The-Dorkknight/simplecad/blob/main/THIRD_PARTY.md" rel="noopener noreferrer"&gt;THIRD_PARTY.md&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Early release (0.12.0).&lt;/strong&gt; Tested in FreeCAD 1.1.4 on Linux. SimpleFlow's RUN button has been run for real against OpenFOAM v1912 (ESI) only; OpenFOAM Foundation 13, Windows and Mac are not tested yet; see &lt;a href="https://github.com/The-Dorkknight/simplecad/blob/main/RESEARCH.md" rel="noopener noreferrer"&gt;RESEARCH.md&lt;/a&gt;. SimpleStress results are a quick check, not a certified analysis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;FreeCAD made simple: one pack, four workbenches.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SimpleCAD&lt;/strong&gt;: a decluttered, Fusion 360-style sketch-and-solid workbench. FreeCAD's screen is full of tools most people never touch; SimpleCAD hides them (nothing is removed) and puts sketch → solid front and centre, with one big red button that always does the next obvious thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SimpleStress&lt;/strong&gt;, &lt;strong&gt;SimpleFlow&lt;/strong&gt;, &lt;strong&gt;SimpleSnap&lt;/strong&gt;: foolproof, step-by-step simulations (will it break? how does air or water move? put the parts together). Each locks you into a short numbered pipeline: you can't skip a step, every refused click explains itself in plain English, and the same big red caution-tape button runs the next thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Ask it&lt;/th&gt;
&lt;th&gt;You do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SimpleStress&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Will my part break?&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Pick one part or several, tap a material tile for each (PLA, PETG, ABS, steel, stainless, pine, oak, plywood...), say &lt;strong&gt;which way up it was printed&lt;/strong&gt; or &lt;strong&gt;which way the grain runs&lt;/strong&gt;, &lt;strong&gt;bolt&lt;/strong&gt; parts together with virtual M3-M6 bolts (no holes needed), &lt;strong&gt;paint&lt;/strong&gt; the faces that are glued or bolted, &lt;strong&gt;pin&lt;/strong&gt; holes, &lt;strong&gt;click&lt;/strong&gt; where it gets pushed (a small spot, shown as a twinkling red disc, or the whole face), say how hard in N, kg or lb, &lt;strong&gt;tilt&lt;/strong&gt; the push with two sliders, press RUN. You get &lt;em&gt;STRONG / PROBABLY OK / RISKY / WILL BREAK&lt;/em&gt; and a red dot on the weakest spot.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SimpleFlow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;How does air or water move round or through it?&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Choose AROUND (a wind tunnel is built for you) or THROUGH A HOLE (click the rim of each opening: IN or OUT; the water inside is worked out for you), pick air / water / oil, set the speed and tilt the wind. It picks laminar or turbulent for you and builds the CfdOF case.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SimpleSnap&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Put these parts together.&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Like Lego: click a peg, hole or flat face; it lists what fits (pegs into matching holes, flat onto flat), you choose STUCK / SPINS / SLIDES, press SNAP IT. The peg goes all the way in and the part lands the right way up.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fma77fe555rpob4z2jwhq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fma77fe555rpob4z2jwhq.png" alt="SimpleStress result: stress colours, glued end, spot force" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All screenshots are real FreeCAD 1.1.4 windows (Linux, virtual display, scripted clicks), except the deck close-ups &lt;code&gt;deck.webp&lt;/code&gt; / &lt;code&gt;swirl.webp&lt;/code&gt;, which are renders.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SimpleCAD
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brushed-aluminium instrument panel&lt;/strong&gt;: the deck is a screwed-on brushed-metal plate with engraved, recessed modules (MODE · CONFIRM · VIEW · SYSTEM), machined keys with indicator LEDs and a green phosphor readout; SimpleCAD's tool rows get the same metal and keys. The grain is a seamless generated tile (&lt;code&gt;tools/make_brushed_alu.py&lt;/code&gt;), so it's all offline. Turn the tool-row styling off with &lt;code&gt;ALU_TOOLBARS = False&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The big red button&lt;/strong&gt; (in caution tape, shortcut &lt;strong&gt;Ctrl+Enter&lt;/strong&gt;) does the next obvious thing:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;✏ SKETCH&lt;/strong&gt;: sketch on the selected face, or edit the selected sketch. With nothing selected, the three planes appear in the 3D view: click one, or press the big &lt;strong&gt;TOP / FRONT / SIDE&lt;/strong&gt; buttons. No side-panel list to hunt through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✔ FINISH SKETCH&lt;/strong&gt;: close the sketch and leave it selected, so Extrude / Revolve / Loft is one click.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✔ OK – FINISH STEP&lt;/strong&gt;: when a tool like Pad or Fillet is open, press its OK button.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acid-green ACTIVE badge&lt;/strong&gt; with rising bubbles shows whether you're in SKETCH, SOLID or TOOL mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🎯 SNAP TO FACE&lt;/strong&gt;: look straight at the selected face (or the sketch you're in) and zoom to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⏎ ENTER button&lt;/strong&gt;: pulses red whenever a tool (Pad, Pocket, Fillet...) is open and ready to confirm; click it, or just press Enter on the keyboard. (Enter is left alone in sketch mode, where it confirms typed dimensions.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whole-solid tools&lt;/strong&gt; (Solid toolbar): &lt;strong&gt;New body&lt;/strong&gt; (works from the start page too), &lt;strong&gt;Duplicate&lt;/strong&gt;, &lt;strong&gt;Move / turn&lt;/strong&gt; (drag arrows and rings), &lt;strong&gt;Combine&lt;/strong&gt;, &lt;strong&gt;Subtract&lt;/strong&gt; (first click kept, the rest cut away) and &lt;strong&gt;Intersect&lt;/strong&gt;. Every result is a new body you can keep sketching on; originals are hidden, not deleted. Each says in plain English why it won't (e.g. "These solids don't touch, so they can't become one solid").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make component&lt;/strong&gt;: select bodies that belong together (a lid and its knob, a case and its clips) and they become one component (a folder that moves as one piece, like a Fusion 360 component). Nothing moves on screen. SimpleSnap snaps a component as one part. Select a component before &lt;strong&gt;New body&lt;/strong&gt; to start a body inside it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy2z7dzngb22idp0k1t1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy2z7dzngb22idp0k1t1l.png" alt="Combine, subtract, duplicate in FreeCAD 1.1.4" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Light on old PCs&lt;/strong&gt;: the deck is drawn once and reused, and the badge bubbles and ENTER pulse only animate briefly when something changes, so FreeCAD sits at about 3 % CPU when idle (0.9.3 kept a core about 40 % busy).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sketch on a solid, snap to it&lt;/strong&gt;: a new sketch on a solid gets that solid's edges in the sketch plane as pink reference lines (like Fusion 360), so your lines snap to them and can be made coincident. &lt;strong&gt;Use solid edges&lt;/strong&gt; and &lt;strong&gt;Project&lt;/strong&gt; (click any other edge) are first in the sketch tool row.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fwdlw4zfjhgvmbzip04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fwdlw4zfjhgvmbzip04.png" alt="Sketching on a solid: its edges come in pink" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only the everyday tools&lt;/strong&gt;, on their own row under the deck, and the row swaps automatically between sketch tools and solid tools. On a smaller screen (e.g. a 1366 px laptop) the labels drop to icons only, so no tool falls off the end (hover for its name). Optional auto-extrude after finishing a sketch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⋯ More tools&lt;/strong&gt;: every Part Design / Sketcher command, plus "Show all hidden toolbars" and a jump to the full Part Design workbench.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stays put&lt;/strong&gt;: FreeCAD 1.x jumps to the Sketcher workbench when you open a sketch, and to Part Design whenever a Pad/Pocket/Fillet... dialog opens. SimpleCAD hops straight back without resetting anything (Declutter, deck and buttons stay as they were), and steps aside if FreeCAD ever keeps fighting it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tidy panels&lt;/strong&gt;: model tree, task panel, report view and Python console become tabs on the left, and the Tasks tab comes forward when a dialog opens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🧭 Navigation readout&lt;/strong&gt; (the green phosphor display) cycles navigation styles (all orbit around the centre of the screen unless noted):

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Fusion Trackpad&lt;/em&gt;: two-finger swipe pans, Shift or Option + two-finger swipe orbits, pinch zooms, two-finger double-tap fits everything; two-finger click-drag also orbits.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;TinkerCAD&lt;/em&gt;: right-drag (two-finger click-drag) orbits, middle-drag pans, wheel zooms.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;FreeCAD Touchpad&lt;/em&gt;: FreeCAD's own touchpad style.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Fusion Mouse&lt;/em&gt; and &lt;em&gt;Cursor Mouse&lt;/em&gt; (orbit around what's under the mouse) need a mouse with a middle button, so on a Mac the button skips them; they're still in &lt;strong&gt;More tools → 🧭 Navigation&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Reverse trackpad directions or restore FreeCAD's original navigation from the same menu.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🌀 Rainbow swirl&lt;/strong&gt;: the selected face gets a smooth, see-through rainbow spiral so you can always see what's active (opacity: &lt;code&gt;SWIRL_OPACITY&lt;/code&gt;). It animates gently (10 fps) and freezes to a static swirl by itself if the PC struggles; toggle it or make it static in &lt;strong&gt;More tools → 🎨 Look&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🌈 Rainbow faces&lt;/strong&gt;: every face gets its own bright, see-through colour, updated automatically after each Pad/Pocket (toggle in &lt;strong&gt;More tools → 🎨 Look&lt;/strong&gt;; transparency and colour strength at the top of &lt;code&gt;SimpleCADDeck.py&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Looks&lt;/strong&gt;: solids go even more see-through while you sketch. &lt;strong&gt;🌙 Luminous&lt;/strong&gt; (high contrast: near-black background, glowing cyan solids, neon selection) or &lt;strong&gt;☀ Clean&lt;/strong&gt; (light, soft blue-grey). Originals are backed up; &lt;strong&gt;More tools → 🎨 Look → Restore&lt;/strong&gt; puts them back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The look stays in SimpleCAD.&lt;/strong&gt; Switch to SimpleStress, SimpleFlow, Part Design or any other workbench and your solids get their own colours back and FreeCAD its own background; the look returns when you come back. SimpleSnap is the exception: it assembles the same bodies you model, so they keep SimpleCAD's look there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DECLUTTER key&lt;/strong&gt; (amber LED when on): hides every remaining toolbar, panel and the status bar (never the workbench switcher). Press again to undo. Nothing it hides is remembered by FreeCAD, so a restart, with or without SimpleCAD, always brings everything back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opens in SimpleCAD&lt;/strong&gt;: FreeCAD starts in SimpleCAD, and a new Part Design document (e.g. the Start page's &lt;em&gt;Parametric Body&lt;/em&gt;) opens in SimpleCAD too. Turn off with &lt;strong&gt;More tools → Start FreeCAD in SimpleCAD&lt;/strong&gt; (your previous start workbench comes back). The &lt;strong&gt;LUMINOUS key&lt;/strong&gt; (green LED) switches the high-contrast 3D view.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step by step
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Start FreeCAD and pick &lt;strong&gt;SimpleCAD&lt;/strong&gt; (it opens there by default).&lt;/li&gt;
&lt;li&gt;Click the big red button (or &lt;strong&gt;Ctrl+Enter&lt;/strong&gt;) with nothing selected, then click TOP / FRONT / SIDE in the 3D view.&lt;/li&gt;
&lt;li&gt;Draw, then press the button again to finish the sketch; press &lt;strong&gt;Extrude&lt;/strong&gt; (or let auto-extrude do it).&lt;/li&gt;
&lt;li&gt;While a tool (Pad, Pocket, Fillet...) is open, press &lt;strong&gt;Enter&lt;/strong&gt; or the ⏎ button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More tools&lt;/strong&gt; holds everything else, including &lt;strong&gt;Show all hidden toolbars&lt;/strong&gt; and &lt;strong&gt;About&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftzuknnih6p7fx15h9myt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftzuknnih6p7fx15h9myt.webp" alt="Rainbow swirl" width="620" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Customise
&lt;/h3&gt;

&lt;p&gt;Everything tweakable is at the top of &lt;code&gt;SimpleCADDeck.py&lt;/code&gt;: which tools appear, icon size, colours, themes, navigation presets, trackpad speeds and the bubble animation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simulations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SimpleStress:&lt;/strong&gt; PARTS &amp;gt; MATERIAL &amp;gt; DIRECTION &amp;gt; BOLTS &amp;gt; HOLD &amp;gt; FORCES &amp;gt; RUN &amp;gt; RESULT&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The only solid in the document is picked automatically. Ctrl+click several solids to test them bolted together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Materials&lt;/strong&gt; are big colourful tiles (drawn in code, no image files): 3D printed (PLA, PETG, ABS, ASA, nylon, polycarbonate, resin), metal (mild steel, stainless, aluminium, brass), wood &amp;amp; board (pine, oak, plywood, MDF) and acrylic. Each shows its strength along and across its weak or strong direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DIRECTION&lt;/strong&gt; (only when it matters): how it sat on the printer (flat, on its side, on its end, or click the face that was on the bed), which way the grain runs (or click the end grain), or which way a sheet's thickness runs. A blue arrow shows it in the 3D view. The stiffness and strength then really depend on it: a PLA bracket printed on its end can be half as strong as one printed flat, and the result tells you when "layers pulled apart" is the weak spot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BOLTS&lt;/strong&gt; (several parts only): pick M3, M4, M5 or M6 and stainless A2-70 or steel 8.8, press PLACE BOLTS and click where each bolt head goes. The bolt runs straight through into the part it touches; &lt;strong&gt;holes are optional&lt;/strong&gt;. A bolt and nut are drawn in the 3D view; click one again to remove it. Each bolt is checked for stretching, shearing, its head or nut pulling through the part, and crushing the hole wall, and the result lists every part and every bolt. Parts touch only where bolted (cautious).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GLUE&lt;/strong&gt; = stuck solid. &lt;strong&gt;PIN&lt;/strong&gt; = a bolt through a round hole: it can turn but not slide off. A single pin on its own is refused (it would swing like a door).&lt;/li&gt;
&lt;li&gt;Forces: &lt;strong&gt;SPOT&lt;/strong&gt; (default): click exactly where it pushes; the force goes on a small circle there (set its size, e.g. a finger is about 15 mm, an M4 washer 9 mm), shown as a red twinkling disc. Click again to move it. &lt;strong&gt;WHOLE FACE&lt;/strong&gt;: paint faces and the push is spread over them. Set how hard in &lt;strong&gt;N, kg or lb&lt;/strong&gt; (kg/lb = the pull of that weight hanging on it; newtons always shown; your unit is remembered), PUSH IN or PULL OUT, then &lt;strong&gt;TILT&lt;/strong&gt; (0 straight, 90 sideways) and &lt;strong&gt;SWING&lt;/strong&gt; (which way it leans) with a live compass and a red arrow. Optional self-weight.&lt;/li&gt;
&lt;li&gt;The part wears its material in the 3D view: layer lines the way it was printed, wood grain along the grain, brushed metal, so a wrong direction is easy to spot.&lt;/li&gt;
&lt;li&gt;A face can't be both held and pushed. Nothing runs until something holds it and something pushes it.&lt;/li&gt;
&lt;li&gt;The mesh is coarsened automatically so older PCs (7th-gen Core i3) cope.&lt;/li&gt;
&lt;li&gt;CLEAN UP removes everything the study added, including FreeCAD's own result objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwxks2dvxshoiox51ngf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwxks2dvxshoiox51ngf.png" alt="Material tiles, and the part showing PETG's layer lines" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92m3oind3ja484unpn2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92m3oind3ja484unpn2g.png" alt="Oak: the grain shown along the shelf" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqeq8d81em0hklgbgiizu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqeq8d81em0hklgbgiizu.png" alt="Which way up it was printed" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fninkq4pripofheow612q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fninkq4pripofheow612q.png" alt="A spot force: red twinkling disc where it pushes, in kg" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5xob82bsbagdv403qw90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5xob82bsbagdv403qw90.png" alt="Two parts bolted with two M4s" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz17eua4n50xohxjl74d0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz17eua4n50xohxjl74d0.png" alt="Bolted result: every part and bolt listed" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SimpleFlow:&lt;/strong&gt; PART &amp;gt; KIND &amp;gt; FLUID &amp;gt; FLOW &amp;gt; CHECK &amp;gt; RUN &amp;gt; RESULT&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AROUND builds a wind-tunnel box aligned with the wind (3 lengths upstream, 8 downstream, 3 each side), cuts your part out, and labels inlet, outlet and slip side walls.&lt;/li&gt;
&lt;li&gt;THROUGH A HOLE: pick the part with the hole or channel in it (a drilled block, a pipe fitting, a manifold). Every opening on a flat face is found and shown as a grey disc. Tap IN or OUT next to each, or turn on IN / OUT and click the &lt;strong&gt;rim&lt;/strong&gt; of the hole (or its inside wall near that end: the nearest end is used, never the whole wall). The fluid inside is worked out for you; if the channel also comes out somewhere you have not marked, it names that opening instead of letting the water leak. Unrelated holes (e.g. mounting holes) are ignored. Several inlets may face different ways.&lt;/li&gt;
&lt;li&gt;THE PART IS THE FLUID: if you modelled the water itself (a solid shaped like the inside of the pipe), paint the faces where it enters and leaves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujooq0slsy6c2pnzzivf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujooq0slsy6c2pnzzivf.png" alt="Water through a drilled hole: IN at the top, OUT at the bottom" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CHECK shows the Reynolds number and whether it will be laminar or turbulent, and refuses air above 100 m/s (beyond what this setup can model).&lt;/li&gt;
&lt;li&gt;RUN is one green button, like SimpleStress's: it builds the CfdOF case, meshes the part (Gmsh), runs OpenFOAM on every core, with a live progress line ("Solving, step 120 of 2000") and a red STOP that really ends the solver. When it finishes it jumps to RESULT. Nothing to open in CfdOF any more (small MESH PANEL / SOLVER PANEL buttons stay for experts). RESULT reads the solver log: SETTLED, STILL RUNNING, FINISHED BUT NOT SETTLED, or BLEW UP (with advice), plus how many cells and how long it took.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F17we81jb3lpkcl1wxb9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F17we81jb3lpkcl1wxb9j.png" alt="SimpleFlow's RUN step: one green button" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbh9hycs6e9oxcp8kolqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbh9hycs6e9oxcp8kolqd.png" alt="Wind direction" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SimpleSnap:&lt;/strong&gt; PARTS &amp;gt; BASE &amp;gt; SNAP &amp;gt; DONE&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tick parts to add them. &lt;strong&gt;Your own bodies go into the assembly, no copies are made&lt;/strong&gt;: snapping moves the real part, so SimpleCAD and SimpleStress see it where it was snapped. Parts stay where they are; one that overlaps another is slid aside first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Components:&lt;/strong&gt; bodies that move together (a lid and its separate knob) can be added as &lt;strong&gt;one component&lt;/strong&gt; (select them, then &lt;strong&gt;+ ADD SELECTED AS ONE COMPONENT&lt;/strong&gt;), or made beforehand with SimpleCAD's &lt;strong&gt;Make component&lt;/strong&gt;. Each body on its own is its own component.&lt;/li&gt;
&lt;li&gt;An assembly already in the file is picked up again when you come back (also after reopening the file).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;START AGAIN&lt;/strong&gt; puts every part back exactly where it was (same place, same folder) and splits components SimpleSnap made back into their bodies.&lt;/li&gt;
&lt;li&gt;The base is fixed to the table (first part by default).&lt;/li&gt;
&lt;li&gt;Click a face on the part you want to move. Matching faces on other parts are listed best-first (a perfect fit is chosen for you). Choose STUCK, SPINS, SLIDES, SPINS &amp;amp; SLIDES or BALL JOINT where it makes sense, then press the big red &lt;strong&gt;SNAP IT&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTHER END&lt;/strong&gt; pushes a peg in from the other side of a through-hole; &lt;strong&gt;FLIP LAST&lt;/strong&gt;, &lt;strong&gt;UNDO LAST&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Snapping two parts that are both already attached is refused (it would jam the assembly). NEXT unlocks only when nothing is loose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvpmyyd0sx88l2a2i4uf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvpmyyd0sx88l2a2i4uf.png" alt="Peg into hole" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqowhybx8ggz7mbz7fyat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqowhybx8ggz7mbz7fyat.png" alt="A lid snapped onto a base in SimpleSnap, then tested in SimpleStress: one lid, the spot exactly where it was clicked" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Files
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;InitGui.py&lt;/code&gt;, &lt;code&gt;SimpleCAD*.py&lt;/code&gt;, &lt;code&gt;icons/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;SimpleCAD itself (deck, solid tools, trackpad, swirl, metal panel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;SimpleStress/&lt;/code&gt;, &lt;code&gt;SimpleFlow/&lt;/code&gt;, &lt;code&gt;SimpleSnap/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The simulations (each: &lt;code&gt;InitGui.py&lt;/code&gt;, &lt;code&gt;*Boot.py&lt;/code&gt;, &lt;code&gt;*UI.py&lt;/code&gt; screens, &lt;code&gt;*Logic.py&lt;/code&gt; plain-Python rules, &lt;code&gt;*Backend.py&lt;/code&gt; FreeCAD calls)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;simplewiz/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The simulations' shared core: step-locking wizard, caution-tape panel, face picker, geometry, 3D overlays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;install.py&lt;/code&gt; + launchers&lt;/td&gt;
&lt;td&gt;Installer window (install, start FreeCAD, check, uninstall)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tests/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unit/UI tests (no FreeCAD needed) and &lt;code&gt;tests/freecad/&lt;/code&gt; scripts that run in real FreeCAD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RESEARCH.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sources, measurements, decisions, and what is not verified yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HOW_IT_WORKS.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What it reads, changes and never does&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Build and test
&lt;/h2&gt;

&lt;p&gt;Nothing to compile. The heavy work is done by FreeCAD, CalculiX, Gmsh, OpenFOAM and FreeCAD's assembly solver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements-dev.txt                    &lt;span class="c"&gt;# PySide6, only for the tests&lt;/span&gt;
&lt;span class="nv"&gt;QT_QPA_PLATFORM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;offscreen python3 &lt;span class="nt"&gt;-m&lt;/span&gt; unittest discover &lt;span class="nt"&gt;-s&lt;/span&gt; tests
&lt;span class="c"&gt;# real FreeCAD (Linux, AppImage extracted to ./squashfs-root):&lt;/span&gt;
&lt;span class="nv"&gt;FREECAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./squashfs-root/AppRun tests/freecad/run_all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Version
&lt;/h2&gt;

&lt;p&gt;One version for the whole pack. It is in SimpleCAD's &lt;strong&gt;About&lt;/strong&gt; box, the deck's tooltip and the status bar, in every simulation panel's title and status line (e.g. &lt;code&gt;SimpleStress 0.12.0 (Built 2026-10-16 00:58)&lt;/code&gt;), and in &lt;code&gt;python3 install.py --version&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/simplecad" rel="noopener noreferrer"&gt;The-Dorkknight/simplecad on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>software</category>
      <category>tools</category>
    </item>
    <item>
      <title>Skipper Flipper</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Wed, 14 Oct 2026 20:11:04 +0000</pubDate>
      <link>https://dev.to/open-madscience/skipper-flipper-4pk1</link>
      <guid>https://dev.to/open-madscience/skipper-flipper-4pk1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdx5e6bq7lid62h26fzhx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdx5e6bq7lid62h26fzhx.jpg" alt="cover"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An experimental ESP32 pseudo Klipper port.&lt;/em&gt; (Repository and package name: &lt;code&gt;esp-printhost&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPL-3.0&lt;/strong&gt; · Linux (Mint / Ubuntu) · C++17 core + Python tools · &lt;a href="https://The-Dorkknight.github.io/esp-printhost/" rel="noopener noreferrer"&gt;Project page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Licensed under the GNU General Public License v3.0, see &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/LICENSE" rel="noopener noreferrer"&gt;LICENSE&lt;/a&gt;. &lt;strong&gt;No warranty and no liability:&lt;/strong&gt; this is provided as is. You use it at your own risk, on your own hardware, and the author is not responsible for damage to boards, printers or anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is
&lt;/h2&gt;

&lt;p&gt;An idea for reviving old, donated 3D printers: a cheap ESP32-S3 becomes the "brain" (WiFi, web UI, text config, motion planning) and the printer's original board becomes a simple slave running a modified Klipper MCU firmware. The full plan is in &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/DESIGN.md" rel="noopener noreferrer"&gt;docs/DESIGN.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target hardware: ESP32-S3 only&lt;/strong&gt; for the host ("master"). &lt;strong&gt;Status: mostly working, needs tuning and proper testing.&lt;/strong&gt; On real hardware (see &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/HARDWARE_LOG.md" rel="noopener noreferrer"&gt;docs/HARDWARE_LOG.md&lt;/a&gt;), installed in a WEEDO Tina 2 with a BTT SKR Pico: the WiFi status page, console and &lt;code&gt;printer.cfg&lt;/code&gt; editor; the Klipper link, clock sync, board configuration and recovery from board power cycles; temperatures, heating, PID tuning and Klipper's heater safety checks; the motor test; TMC2209 set-up; stallGuard triggering; the hotend fan. &lt;strong&gt;Not yet confirmed:&lt;/strong&gt; the part-cooling fan and the third fan output (possibly a broken wire; will update when tested). &lt;strong&gt;Needs tuning:&lt;/strong&gt; stallGuard threshold, motor current, speeds, &lt;code&gt;rotation_distance&lt;/code&gt;. &lt;strong&gt;Only simulated so far:&lt;/strong&gt; moves, full homing and probing, and printing a file. The whole story, including every mistake: &lt;strong&gt;&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/paper/A_FRANKENSTEIN_EXPERIENCE.md" rel="noopener noreferrer"&gt;A Frankenstein Experience&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/paper/A_FRANKENSTEIN_EXPERIENCE.pdf" rel="noopener noreferrer"&gt;PDF&lt;/a&gt;).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Klipper wire protocol (CRC16, VLQ ints, framing, resync, data dictionary)&lt;/td&gt;
&lt;td&gt;Unit-tested; &lt;strong&gt;works on hardware&lt;/strong&gt; (ESP32-S3 to SKR Pico)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESP32-S3 firmware (&lt;code&gt;firmware/esp32s3&lt;/code&gt;): WiFi access point, status page, console, &lt;code&gt;printer.cfg&lt;/code&gt; editor, test bench, printer page, the Klipper host's jobs (heaters, TMC2209, moves, homing, printing from flash). Shipped ready to flash&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;On hardware&lt;/strong&gt; (to 2026-10-14): link, heating, PID, motor test, TMC2209, stallGuard trigger. Moves, homing and printing: simulation only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;skr-pico-uart&lt;/code&gt; printer-board firmware (Klipper v0.13.0 + segment queue, serial on UART0)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Works on hardware&lt;/strong&gt;: link, heater, thermistor, steppers, TMC UART, DIAG pins. The segment queue is not used by the ESP32 host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Segment-queue core (&lt;code&gt;firmware/skipper/segq_core.*&lt;/code&gt;, shared by the firmware and the PC tests)&lt;/td&gt;
&lt;td&gt;Working in simulation: exact step counts, step spacing, ring buffer, start threshold, underrun, stop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion planner + G-code engine (lookahead, junction deviation, trapezoids, pressure advance, G-code state)&lt;/td&gt;
&lt;td&gt;Working in simulation: output is run through the real core and checked for exact positions, speed and acceleration limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other board presets (generic RP2040, Creality 4.2.x, RAMPS mega2560, Ender-3 1.1.x, USB SKR Pico)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Compile only&lt;/strong&gt;; never run on hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flasher app (&lt;code&gt;flasher.py&lt;/code&gt;): Slave tab (printer board) and Master tab (ESP32-S3)&lt;/td&gt;
&lt;td&gt;ESP32-S3 flashing &lt;strong&gt;used on hardware&lt;/strong&gt; on Linux Mint. Other paths (SD card, avrdude, Windows/macOS) tested with mocks only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firmware build tool (CLI + Slave tab "My own build")&lt;/td&gt;
&lt;td&gt;Works on Linux (sandbox and the author's Linux Mint laptop); Windows needs WSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mcu_probe&lt;/code&gt;, &lt;code&gt;fake_mcu.py&lt;/code&gt;, one-command setup, Tools window&lt;/td&gt;
&lt;td&gt;Working on Linux Mint. Probe tried on an SKR Pico over USB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real printing&lt;/td&gt;
&lt;td&gt;Built and &lt;strong&gt;simulated only&lt;/strong&gt; (upload, print from flash, pause / resume / cancel); not yet run on the printer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fans&lt;/td&gt;
&lt;td&gt;Hotend fan &lt;strong&gt;confirmed on hardware&lt;/strong&gt;; part-cooling fan and the third fan output not yet (possibly a broken wire, will update when tested)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total checks: 1,029 C++ unit checks and 187 Python tests (including power-cycle, stallGuard and printing runs against a simulated board), all passing in the sandbox. Passing tests mean the maths and bookkeeping are right, not that a printer will behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;The GUI building the tools and running the unit tests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwwdyfovyfzcafenw0peq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwwdyfovyfzcafenw0peq.webp" alt="Running mcu_probe against the fake MCU"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both screenshots use the &lt;strong&gt;simulated&lt;/strong&gt; board, not real hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flash a board (Linux, macOS, Windows)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 flasher.py          (Windows: double-click Flasher.bat; macOS: Flasher.command)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need Python 3.8+ with Tk (Linux: &lt;code&gt;sudo apt install python3-tk&lt;/code&gt;; on Windows and macOS the installer from python.org includes Tk). No toolchain is needed: firmware for every board preset (five boards, plus a serial variant of the SKR Pico) ships in &lt;code&gt;firmware/prebuilt/&lt;/code&gt;. Pick the &lt;strong&gt;Slave&lt;/strong&gt; tab, choose your board, press &lt;strong&gt;Flash...&lt;/strong&gt; and confirm. RP2040 boards: hold BOOTSEL while plugging in USB. STM32 boards such as the Creality 4.2.x: put the SD card in this computer, &lt;strong&gt;Browse&lt;/strong&gt; to it, flash, then move the card to the printer and power-cycle it. AVR boards need &lt;code&gt;avrdude&lt;/code&gt; installed. The &lt;strong&gt;Master&lt;/strong&gt; tab flashes an ESP32-S3 with esptool (Install esptool, then &lt;strong&gt;Use shipped firmware&lt;/strong&gt;, pick the port, Flash). The port box fills itself with the ESP32-S3's port when it can tell which one it is (otherwise pick it). The shipped image starts a WiFi network &lt;code&gt;SkipperFlipper-XXXX&lt;/code&gt; (WPA2, default password &lt;code&gt;skipperflipper&lt;/code&gt;, change it before real use) with a page at &lt;code&gt;http://192.168.4.1/&lt;/code&gt;, a live log at &lt;code&gt;/console&lt;/code&gt; and &lt;code&gt;printer.cfg&lt;/code&gt; at &lt;code&gt;/config&lt;/code&gt;; it talks Klipper to the printer board over UART (identify, clock sync, LEDs and thermistors from &lt;code&gt;printer.cfg&lt;/code&gt;, confirmed on hardware; heaters, fans, TMC2209 drivers and motor tests from the test bench at &lt;code&gt;/test&lt;/code&gt;, simulated only so far). Moves, homing and printing a file (Printer page at &lt;code&gt;/printer&lt;/code&gt;) are built and simulated, not yet run on hardware. Logs appear on the chip's native USB port. &lt;strong&gt;ESP32-S3 only&lt;/strong&gt;: other ESP32 chips (original, S2, C3) are not supported, and esptool refuses to write if it finds a different chip. You can also flash your own &lt;code&gt;.bin&lt;/code&gt;. To connect the ESP32-S3 to an SKR Pico with four jumper wires, see &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/WIRING_SKR_PICO.md" rel="noopener noreferrer"&gt;docs/WIRING_SKR_PICO.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdxsgu81xrdozf74pqjjp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdxsgu81xrdozf74pqjjp.webp" alt="The Flasher, Slave tab with the Creality 4.2.x preset"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only the &lt;code&gt;skr-pico-uart&lt;/code&gt; firmware has run on a board, and only for the link and its on-board LED&lt;/strong&gt;; the other presets have never been flashed. Read &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/FIRMWARE.md" rel="noopener noreferrer"&gt;docs/FIRMWARE.md&lt;/a&gt; first, and do not flash a printer you cannot recover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start (Linux Mint / Ubuntu / Debian)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/The-Dorkknight/esp-printhost
&lt;span class="nb"&gt;cd &lt;/span&gt;esp-printhost
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 printhost.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one command installs what is missing (build tools, cmake, zlib, python3-tk, acl), gives you access to USB serial ports without logging out, stops ModemManager and brltty from grabbing printer boards, builds &lt;code&gt;mcu_probe&lt;/code&gt;, runs the tests, and opens the GUI as your normal user. It is safe to re-run. &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/HOW_IT_WORKS.md" rel="noopener noreferrer"&gt;HOW_IT_WORKS.md&lt;/a&gt; lists exactly what it changes on your system.&lt;/p&gt;

&lt;p&gt;Options: &lt;code&gt;--check&lt;/code&gt; (report only, change nothing), &lt;code&gt;--no-gui&lt;/code&gt;, &lt;code&gt;--clean&lt;/code&gt;, &lt;code&gt;--yes&lt;/code&gt;, &lt;code&gt;--version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If anything fails it prints the path of a log file in &lt;code&gt;~/esp-printhost-logs/&lt;/code&gt;. Attach that when you open an issue.&lt;/p&gt;

&lt;p&gt;macOS: the C++ library and tests build (&lt;code&gt;python3 host/tools/build.py&lt;/code&gt;), but &lt;code&gt;mcu_probe&lt;/code&gt; is Linux-only (it uses Linux's &lt;code&gt;termios2&lt;/code&gt; for 250000 baud) and the one-command setup is Linux-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the GUI
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build tools&lt;/strong&gt; compiles everything and runs the unit tests. Output is colour-coded per tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fake MCU: Start&lt;/strong&gt; runs a pretend Klipper board (no hardware). Its port appears in the Port box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run probe&lt;/strong&gt; connects, reads the board's pin list into the &lt;strong&gt;Blink pin&lt;/strong&gt; dropdown, and prints uptime and state. Tick &lt;strong&gt;Blink pin&lt;/strong&gt; and pick a pin to also toggle it 10 times.&lt;/li&gt;
&lt;li&gt;The output box is selectable (Ctrl+C, right-click, &lt;strong&gt;Copy all&lt;/strong&gt;, &lt;strong&gt;Save as...&lt;/strong&gt;). Everything is also logged to &lt;code&gt;~/esp-printhost-logs/&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it without hardware
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 host/tools/fake_mcu.py            &lt;span class="c"&gt;# prints /dev/pts/N&lt;/span&gt;
./host/build/mcu_probe /dev/pts/N 250000 &lt;span class="nt"&gt;--pins&lt;/span&gt; &lt;span class="nt"&gt;--blink&lt;/span&gt; PA5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try it on a real board
&lt;/h2&gt;

&lt;p&gt;You need a board running &lt;strong&gt;stock Klipper MCU firmware&lt;/strong&gt; (build it from Klipper with &lt;code&gt;make menuconfig&lt;/code&gt;; this project does not ship firmware yet). Stop Klipper or anything else using the port, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./host/build/mcu_probe /dev/serial/by-id/&amp;lt;your-board&amp;gt; 250000 &lt;span class="nt"&gt;--pins&lt;/span&gt; &lt;span class="nt"&gt;--blink&lt;/span&gt; &amp;lt;pin&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--pins&lt;/code&gt; lists the pin names the board accepts. The probe validates your pin name before touching the board, and if the board is already configured it &lt;strong&gt;resets it&lt;/strong&gt; first (the USB device re-appears after about two seconds). Do not run it on a board that is in the middle of a print.&lt;/p&gt;

&lt;p&gt;Tested so far: BTT SKR Pico (RP2040, Klipper v0.10.0-181). The probe identified it, read 31 pin names, reset it, configured &lt;code&gt;gpio24&lt;/code&gt; and sent 10 toggles with 0 CRC errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project layout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printhost.py          one-command setup + launcher
flasher.py            the Flasher window (also Flasher.bat / Flasher.command)
HOW_IT_WORKS.md       what it reads, changes and never does
docs/                 DESIGN, PROTOCOL (draft v1), ENGINE, FIRMWARE, TASKS, project page
firmware/skipper/     segq_core (portable C core) + segq.c (Klipper glue)
firmware/esp32s3/       ESP32-S3 firmware (ESP-IDF project) + build.sh
firmware/prebuilt-esp32s3/  shipped ESP32-S3 image
firmware/boards/      board presets (.config + .json)
firmware/prebuilt/    shipped firmware per board (+ pin tables); refresh with skipper_fw.py bundle
firmware/test/        PC simulation of the Klipper glue
host/                 portable C++17 core (also an ESP-IDF component, untested)
  include/ph/         wire, msgproto, json_mini, config_ini, gcode, motion, planner, engine
  src/  test/         implementation + 480 unit tests
  tools/              build.py, skipper_fw.py, printhost_gui.py (+ fw_panel, skipper_theme),
                      mcu_probe.cpp, fake_mcu.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;See &lt;a href="https://github.com/The-Dorkknight/esp-printhost/blob/main/docs/TASKS.md" rel="noopener noreferrer"&gt;docs/TASKS.md&lt;/a&gt;. Next: real Klipper behaviour on the ESP32-S3, in stages: clock sync, configuring the board from &lt;code&gt;printer.cfg&lt;/code&gt;, a harmless first action (LED), temperatures, then heaters with safety checks and motor tests (built, waiting for the hardware test), then real moves and homing, then printing a file. The look is a 1928 black-and-white cartoon (visuals only); a Benchy-themed pass is planned for when real building starts.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/esp-printhost" rel="noopener noreferrer"&gt;The-Dorkknight/esp-printhost on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Bonding Experience- The open-source new frontier of Z-delamination resistance.</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Tue, 13 Oct 2026 15:19:35 +0000</pubDate>
      <link>https://dev.to/open-madscience/a-bonding-experience-2k13</link>
      <guid>https://dev.to/open-madscience/a-bonding-experience-2k13</guid>
      <description>&lt;h3&gt;
  
  
  The open-source new frontier of Z-delamination resistance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A hobbyist's field guide to Z-strength in FDM printing without tripping over patents, and a defensive publication of PinLoK (pinned infill).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By Matthew Armstrong (&lt;a href="https://github.com/The-Dorkknight" rel="noopener noreferrer"&gt;The-Dorkknight&lt;/a&gt;), United Kingdom. First published 13 October 2026.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;Read it:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/blob/main/PAPER.md" rel="noopener noreferrer"&gt;PAPER.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/blob/main/a-bonding-experience-v1.0.pdf" rel="noopener noreferrer"&gt;PDF&lt;/a&gt; · &lt;a href="https://the-dorkknight.github.io/a-bonding-experience/" rel="noopener noreferrer"&gt;web page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu8ymavdity412amyun0h.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu8ymavdity412amyun0h.webp" alt="PinLoK in the model: Grid infill without and with pins" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every strength number for my own ideas here, including Shift-LoK, FlatLock and PinLoK, is theoretical:&lt;/strong&gt; it comes from a 2-D computer simulation with known blind spots (paper, section 3), not from printed and tested parts. They are not product claims and say nothing about anyone else's products or patents. Real-world numbers quoted from others are linked to their sources. &lt;strong&gt;Not legal advice.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  In one minute
&lt;/h2&gt;

&lt;p&gt;Printed parts snap along their layers. The famous fix, staggering walls by half a layer ("brick layers"), is patented in the US. I went looking for every way to get stronger layers &lt;em&gt;without&lt;/em&gt; beads of different heights, ran twelve ideas through a strength model, and wrote down what happened, failures included.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Idea&lt;/th&gt;
&lt;th&gt;Simulated result across the layers (theoretical)&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Loopholes" (variable first layer, smaller or multi-phase staggers)&lt;/td&gt;
&lt;td&gt;about the same as normal; still needs different bead heights&lt;/td&gt;
&lt;td&gt;geometry doesn't care about wording&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different layer height per wall&lt;/td&gt;
&lt;td&gt;−1 to −11 %&lt;/td&gt;
&lt;td&gt;weaker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlatLock (fat/thin walls)&lt;/td&gt;
&lt;td&gt;−4 to −17 %&lt;/td&gt;
&lt;td&gt;weaker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Squish-LoK (over-extruded squish bead)&lt;/td&gt;
&lt;td&gt;−8 to +1 %&lt;/td&gt;
&lt;td&gt;nothing to fill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Squish-KeyLoK (keyed gaps)&lt;/td&gt;
&lt;td&gt;−19 to +3 %&lt;/td&gt;
&lt;td&gt;keys snap off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Wide inner walls&lt;/strong&gt; (about 1.5 × nozzle)&lt;/td&gt;
&lt;td&gt;+2 to +9 %&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;partial success, just a slicer setting&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PinLoK pinned infill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;+16 to +27 %&lt;/strong&gt; against solid infill of the same weight&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;best result; costs print time&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;PinLoK&lt;/strong&gt; builds on Oak Ridge National Laboratory's published Z-pinning. Every few layers the nozzle stops over half of the open gaps in Grid infill and pushes a blob of plastic down into each one; the other half get theirs next time, so the joints never line up. A &lt;strong&gt;potato mode&lt;/strong&gt; keeps each push to about a second for stock printers like the Ender 3. A late discovery: pins that end half-way through a layer, not on a layer line, did far better in the simulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's published
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;To keep it free.&lt;/strong&gt; Once a method is published, nobody can validly patent it afterwards. Section 9 of the paper discloses PinLoK and its variations in buildable detail, and the methods are dedicated to the public.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;To bring makers together.&lt;/strong&gt; Open notebooks let tinkerers build on each other's work instead of repeating it. Print the test plan, post your results as an issue or discussion, fork it, improve it: everyone who contributes gets credited in the next version (paper, section 11).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;To save you the dead ends.&lt;/strong&gt; Most of the ideas here didn't work. Now you know without trying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selfishly:&lt;/strong&gt; more cool 3D-printing techniques come back from the community, and I am now 22.713333…% more employable. High-efficiency synergy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's in here
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Folder&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/blob/main/PAPER.md" rel="noopener noreferrer"&gt;&lt;code&gt;PAPER.md&lt;/code&gt;&lt;/a&gt;, the PDF, &lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/tree/main/docs" rel="noopener noreferrer"&gt;&lt;code&gt;docs/&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;The paper as Markdown, PDF and a web page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/tree/main/figures" rel="noopener noreferrer"&gt;&lt;code&gt;figures/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Every figure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/tree/main/pinlok" rel="noopener noreferrer"&gt;&lt;code&gt;pinlok/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The PinLoK G-code post-processor (Python 3.8+, no dependencies): &lt;code&gt;python3 pinlok/engine.py file.gcode --mode potato&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/tree/main/tests" rel="noopener noreferrer"&gt;&lt;code&gt;tests/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Its tests: &lt;code&gt;python3 -m unittest discover -s tests&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/tree/main/model" rel="noopener noreferrer"&gt;&lt;code&gt;model/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The strength model and every result file (needs numpy, scipy, matplotlib)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/blob/main/PUBLISHING.md" rel="noopener noreferrer"&gt;&lt;code&gt;PUBLISHING.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;How this was published, and what makes it count as published&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full app (LoK Walls: FlatLock, PinLoK and a locked brick-layer pattern, with installers for Cura, PrusaSlicer and OrcaSlicer) is in a separate repository, to be published after ADDMAN, the brick-layer patent holder, has been asked. I haven't contacted them yet; I intend to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cite
&lt;/h2&gt;

&lt;p&gt;See &lt;a href="https://github.com/The-Dorkknight/a-bonding-experience/blob/main/CITATION.cff" rel="noopener noreferrer"&gt;CITATION.cff&lt;/a&gt; (GitHub shows a "Cite this repository" button). Once archived, the DOI goes here: &lt;em&gt;DOI to be added&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/a-bonding-experience" rel="noopener noreferrer"&gt;The-Dorkknight/a-bonding-experience on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SimpleMesh</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Sun, 11 Oct 2026 08:45:17 +0000</pubDate>
      <link>https://dev.to/open-madscience/simplemesh-44dc</link>
      <guid>https://dev.to/open-madscience/simplemesh-44dc</guid>
      <description>&lt;p&gt;&lt;strong&gt;WORK IN PROGRESS: MANY FEATURES MAY BE BROKEN&lt;/strong&gt; · version &lt;code&gt;0.5.0-wip&lt;/code&gt; · pre-release&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free software · GPL-3.0-only&lt;/strong&gt; · Copyright (C) 2026 Matthew Armstrong · &lt;strong&gt;No warranty, no liability: use at your own risk.&lt;/strong&gt; See &lt;a href="https://github.com/The-Dorkknight/simplemesh/blob/main/LICENSE" rel="noopener noreferrer"&gt;LICENSE&lt;/a&gt; and &lt;a href="https://github.com/The-Dorkknight/simplemesh/blob/main/THIRD_PARTY.md" rel="noopener noreferrer"&gt;THIRD_PARTY.md&lt;/a&gt;. Not legal advice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkstlo3k6krhjpp7tswd7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkstlo3k6krhjpp7tswd7.webp" alt="Easy mode: picture buttons down the left, a snowman built from shapes, colours and Print! along the bottom" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;About the screenshots:&lt;/strong&gt; they are the real Blender 4.0.2 window with SimpleMesh loaded, captured on a virtual display (software rendering) while a script moved the mouse and pressed keys (&lt;code&gt;tests/gui_session.py&lt;/code&gt;). &lt;strong&gt;No person, and no child, has used this version by hand yet&lt;/strong&gt;, so how it &lt;em&gt;feels&lt;/em&gt; is unproven. See what is tested.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A Blender add-on that turns Blender into a plain &lt;strong&gt;make-and-print workbench&lt;/strong&gt;. No scenery, no animation timeline, no game-asset tooling. It has two levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Easy mode&lt;/strong&gt; (what Blender now starts in): big picture buttons aimed at a ten-year-old. Build things out of shapes, your own drawings, words and clay, colour them in, press &lt;strong&gt;Print!&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The panel&lt;/strong&gt;: big buttons across the top of the 3D view, there in every mode, for editing STL files exactly: lay flat, move, turn, size, pull a face, cut, drill, measure, mould like clay, with millimetres. It is the same idea as SimpleCAD's deck: a big red button that does the next obvious thing, a green badge that says what is active, and keys for the look.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is the Blender sibling of &lt;a href="https://github.com/The-Dorkknight/simplecad" rel="noopener noreferrer"&gt;SimpleCAD&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy mode
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;To make something:&lt;/strong&gt; click a picture, move the mouse, click to put it down. A shape sits on &lt;strong&gt;whatever is under the mouse&lt;/strong&gt;: the floor, or the top or side of another shape, so things stack and stick the way you expect.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Left side: things to add&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Block, Ball, Tube, Cone, Pyramid, Roof, Ring, Star, Heart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ready-made shapes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Draw&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hold the mouse button and draw a loop on the floor. It becomes a solid shape. Wobbly lines, crossed lines and scribbles still give something solid.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Words&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Type a word or your name and get 3D letters.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Squish and pull the chosen shape (or a new ball) like clay, then press Done. Turn on &lt;strong&gt;Mirror&lt;/strong&gt; for faces and creatures.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ideas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Start from a snowman, house, rocket, robot or car made of shapes, then change it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Open&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Load a model file somebody else made.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;To change a shape:&lt;/strong&gt; click it. Drag it to move it. It has six dots:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dot&lt;/th&gt;
&lt;th&gt;Pull it to...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;white (top corner)&lt;/td&gt;
&lt;td&gt;make it bigger or smaller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;red / green / blue&lt;/td&gt;
&lt;td&gt;make it longer / wider / taller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;orange&lt;/td&gt;
&lt;td&gt;spin it round&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;purple&lt;/td&gt;
&lt;td&gt;tip it over&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Along the bottom&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Undo / Redo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go back a step. Nothing can be broken.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Copy / Bin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Another one the same / throw it away.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hole&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Turns the chosen shape into a hole (it goes red). Push it into another shape; Glue or Print! cuts it out.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Glue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sticks the shapes together into one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Save&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keeps your project so you can carry on another day (it reopens in Easy mode).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Colours&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Colour in the chosen shape. (Colours are for you; a printer prints in whatever plastic is loaded.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Print!&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cuts out the holes, glues what touches, repairs everything, sits it on the bed, tells you if it is too big, and saves the file for the printer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bottom right: &lt;strong&gt;arrow buttons turn the view&lt;/strong&gt;, + and − zoom, the middle one shows everything. You can also drag with the right mouse button to look around.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5i9efcedcd38wb494qq5.webp" alt="Start" width="800" height="450"&gt; What Blender looks like when it starts&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7tjdzcgegiklpt53te8w.webp" alt="Stack" width="800" height="450"&gt; A ball being put on top of a block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftfcex322a2rtbmtmcda3.webp" alt="Dots" width="800" height="450"&gt; The six dots on the chosen shape&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwl6lq61p9e0mlhqk2bs.webp" alt="Taller" width="800" height="450"&gt; Pulling the blue dot: taller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ff8iy0b6qivb0r0pnud.webp" alt="Draw" width="800" height="450"&gt; Draw a loop on the floor...&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe8kaidiq5x2qntjdje7j.webp" alt="Ideas" width="800" height="450"&gt; Ideas: ready-made starters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;Easy&lt;/strong&gt; key on the panel turns Easy mode off and on. With it off you select models by clicking and use the panel's tools described next. To make Blender start with it off, untick &lt;em&gt;Start in Easy mode&lt;/em&gt; in the add-on's preferences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What "anything you can imagine" really means here:&lt;/strong&gt; anything you can build from those shapes, your own drawn outlines, letters and clay, stuck together and with holes cut out. That covers a lot (characters, vehicles, buildings, name tags, cookie cutters, creatures) but it is not everything: no screw threads, no exact engineering fits, and very thin clay shapes may not print.&lt;/p&gt;

&lt;h2&gt;
  
  
  The panel (big buttons)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fda1lm0jzaahfq88pbly2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fda1lm0jzaahfq88pbly2.webp" alt="The panel across the top of the 3D view: red MOULD LIKE CLAY button in caution tape, green SELECT ACTIVE badge, FIND MODEL, twelve tool keys, four system keys" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The big red button&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The next obvious thing. Normally &lt;strong&gt;MOULD LIKE CLAY&lt;/strong&gt;. While a tool is on: &lt;strong&gt;FINISH&lt;/strong&gt; that tool. While moulding: &lt;strong&gt;DONE&lt;/strong&gt;. In one of Blender's own modes: &lt;strong&gt;BACK TO THE MODELS&lt;/strong&gt;. In Blender's own Sculpt Mode before the model is ready: &lt;strong&gt;GET READY TO MOULD&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The green badge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What is active right now: SELECT, EASY, LAY FLAT, CLAY: PULL...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FIND MODEL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Centres the view on the model (same as &lt;strong&gt;F&lt;/strong&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lay flat, Move, Turn, Size, Pull face, Cut, Drill, Measure, Open, Save, Undo, Redo. The key of the tool that is on has its light lit. While moulding they become the seven brushes plus &lt;strong&gt;Brush +&lt;/strong&gt;, &lt;strong&gt;Brush -&lt;/strong&gt; and &lt;strong&gt;Mirror&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Easy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Picture buttons on or off.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Luminous&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Switches between the dark, high-contrast look and the light, clean one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hide clutter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hides Blender's own menu rows and status bar; press again to bring them back. The panel stays.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;More tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exact numbers, shapes and holes, check and repair, view settings, &lt;strong&gt;Look&lt;/strong&gt; (below), and &lt;em&gt;Report a problem&lt;/em&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The panel is drawn inside the 3D view (Blender's own header rows cannot hold big buttons), ends short of the right edge so Blender's view ball stays free, and shrinks its buttons on a narrow window. Hover over a button and a line under the panel says what it does. Switch it off in &lt;strong&gt;More tools ▸ Big-button panel&lt;/strong&gt; and the tools go back to the thin strip in Blender's header.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fymruqibsr6874xbs6a4g.webp" alt="A tool is on" width="800" height="280"&gt; Lay flat is on: its light is lit, the red button says FINISH LAY FLAT&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhoj3if4zj6skccqxqao7.webp" alt="While moulding" width="800" height="280"&gt; While moulding: DONE, and the brushes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyii0pfr0jmdib6dpkndn.webp" alt="Clean look" width="800" height="280"&gt; The Clean look&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flh2kvjva4ikjf8ewox4c.webp" alt="Clutter hidden" width="800" height="280"&gt; Hide clutter: Blender's rows are gone&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Look
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;More tools ▸ Look&lt;/strong&gt; (also in the strip's &lt;strong&gt;View&lt;/strong&gt; pop-up):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Luminous&lt;/strong&gt; (near-black background, cyan models, orange outline), &lt;strong&gt;Clean&lt;/strong&gt; (light background, soft blue-grey) or &lt;strong&gt;Blender default&lt;/strong&gt;. The &lt;strong&gt;Luminous&lt;/strong&gt; key on the panel switches between the first two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rainbow&lt;/strong&gt;: every model gets a colour of its own, which shows at a glance which parts are separate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show the triangles&lt;/strong&gt;: the triangles of the STL drawn on the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;See-through&lt;/strong&gt;, &lt;strong&gt;Show flipped faces (red)&lt;/strong&gt;, &lt;strong&gt;Show size on the model&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv88dakoz49etlyhu4p3o.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv88dakoz49etlyhu4p3o.webp" alt="Rainbow and triangles" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open&lt;/strong&gt; an STL. It arrives in millimetres at a sensible size (a 5200 mm monster is divided by 10 until it fits your bed), sitting on the bed, in the middle of the view. Its size and "fits the bed" are written in the top-left corner all the time.&lt;/li&gt;
&lt;li&gt;Pick a tool on the panel, then &lt;strong&gt;do it on the model&lt;/strong&gt;. The view shows what a click will do before you click. Orbit and zoom keep working. &lt;strong&gt;Esc&lt;/strong&gt; or right-click leaves the tool. &lt;strong&gt;Ctrl+Z&lt;/strong&gt; undoes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save&lt;/strong&gt; writes a millimetre STL as &lt;code&gt;&amp;lt;name&amp;gt;_edited.stl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Lost the model? Press &lt;strong&gt;F&lt;/strong&gt; (or &lt;em&gt;Find&lt;/em&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What you do&lt;/th&gt;
&lt;th&gt;Extras&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lay flat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Move over the model: the flat face under the mouse lights up. Click: that face sits on the bed.&lt;/td&gt;
&lt;td&gt;No Edit Mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Move&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Drag a model: it slides across the bed.&lt;/td&gt;
&lt;td&gt;Shift+drag lifts it. Arrow keys 1 mm (Shift 10, Ctrl 0.1). D = drop to bed, C = centre.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Turn&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Drag a model sideways: it spins in 15° steps and is put back on the bed.&lt;/td&gt;
&lt;td&gt;X / Y / Z choose the direction. Arrow keys = 90°. Type an angle + Enter. Shift = fine.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Drag away from the model = bigger, towards it = smaller. Or &lt;strong&gt;type the longest side in mm + Enter&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;X / Y / Z stretch one direction only. Ctrl = whole millimetres. Scale stays 1.0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pull&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Point at a flat face (it lights up) and &lt;strong&gt;drag it out or in&lt;/strong&gt;: the part gets longer or shorter. Or point and type mm + Enter.&lt;/td&gt;
&lt;td&gt;Steps of 0.5 mm; Ctrl = whole mm; Shift = free. Minus key = inwards when typing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cut&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A red plane follows the mouse over the model; click to cut there. &lt;strong&gt;Or drag a line across the model&lt;/strong&gt; to cut along it.&lt;/td&gt;
&lt;td&gt;X / Y / Z direction. Type a height in mm + Enter. K = keep both / one side. L = lay the cut faces on the bed. Cuts are capped.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Drill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A red circle follows the mouse on the surface; click to drill a round hole.&lt;/td&gt;
&lt;td&gt;Type the diameter + Enter, or + / −. T = through / blind, [ ] = depth. Ctrl = centre of the face.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Measure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Click two points.&lt;/td&gt;
&lt;td&gt;Snaps to corners. Shows the distance and its X / Y / Z parts.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Clay: mould it like clay
&lt;/h3&gt;

&lt;p&gt;Clay is for free-form changes: click a model, press the red &lt;strong&gt;MOULD LIKE CLAY&lt;/strong&gt; button (or the Clay picture in Easy mode), and the panel's keys become seven brushes. Press on the surface and drag. Choosing &lt;em&gt;Sculpt Mode&lt;/em&gt; from Blender's own mode menu ends up in the same place: SimpleMesh does the Clay set-up by itself.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Brush&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pull&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The clay follows the mouse and stretches out. Pull a lump, a horn, a handle out of a flat face.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Build up&lt;/strong&gt; / &lt;strong&gt;Push in&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Rub to raise the surface / press it in.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shift&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slide an area sideways without stretching it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inflate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Swell outwards in all directions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Smooth&lt;/strong&gt; / &lt;strong&gt;Flatten&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Rub out lumps / level an area.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Brush &lt;strong&gt;Size is in millimetres&lt;/strong&gt; and stays the same at any zoom; it starts at a fifth of the model. &lt;strong&gt;Brush +&lt;/strong&gt; and &lt;strong&gt;Brush -&lt;/strong&gt; on the panel change it (or &lt;strong&gt;F&lt;/strong&gt;, then move the mouse). Holding &lt;strong&gt;Ctrl&lt;/strong&gt; does the opposite, holding &lt;strong&gt;Shift&lt;/strong&gt; smooths, &lt;strong&gt;Ctrl+Z&lt;/strong&gt; undoes a stroke. The read-out shows how many triangles the model has and warns in red when it is getting heavy. Press the red &lt;strong&gt;DONE&lt;/strong&gt; button to go back to the normal tools.&lt;/p&gt;

&lt;p&gt;Underneath it is Blender's own sculpting engine, set up for STL work: the model is first given small triangles everywhere (its shape does not change) because an STL's big flat triangles have nothing to mould; more are added under the brush as you work; mirror symmetry is off; and when you press &lt;strong&gt;Done&lt;/strong&gt; the flat areas you did not touch are merged back into big triangles so the file stays small. &lt;strong&gt;Detail&lt;/strong&gt; (the size of the new triangles, in the header row) cannot be set finer than a thirtieth of the brush: a big brush with a tiny Detail makes Blender cut hundreds of thousands of triangles in one drag and stop answering.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhq4ik757g4nyn4gsuw7v.webp" alt="Pull a face" width="800" height="450"&gt; Pull: point at a flat face...&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhor2p3y73xxva92hfcqa.webp" alt="Pull drag" width="800" height="450"&gt; ...and drag it: +10 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feu3r9opxmle3ua2ehr0a.webp" alt="Clay pull" width="800" height="450"&gt; Clay ▸ Pull: a horn pulled out of a flat face&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fktb02u9e66137rf8tr3i.webp" alt="Clay push" width="800" height="450"&gt; Clay ▸ Push in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pop-ups in Blender's header row (and under &lt;strong&gt;More tools&lt;/strong&gt; on the panel) hold the things that need numbers or are used less often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Numbers&lt;/strong&gt;: ÷1000 ÷100 ÷10 ×10 ×1000, inch ↔ mm, set the longest side, exact X/Y/Z, drop / centre / auto lay flat, 90° turns, mirror, hole and cut settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shapes&lt;/strong&gt;: add a block, cylinder, sphere or cone sized in mm. &lt;strong&gt;Make it a hole&lt;/strong&gt; turns any shape into a red wire "hole"; &lt;strong&gt;Cut the holes out&lt;/strong&gt; removes every hole from whatever it touches (no "select the cutter, then the base"). Weld together, separate loose pieces, copy, delete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repair&lt;/strong&gt;: Check (triangles, volume, surface, holes, inside-out faces), one-click Repair, Simplify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View&lt;/strong&gt;: standard views, show everything, looks (Luminous / Clean / Blender default), show flipped faces, laptop navigation (no middle button needed), printer bed size (presets for FlashForge Adventurer 5M, Bambu Lab A1, Creality Ender 3 V2).&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fektkq7drecqrt59p7ne9.webp" alt="Cut" width="800" height="450"&gt; Cut: the plane follows the mouse&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbhj100gqcc92n8s9ajtz.webp" alt="Hole" width="800" height="450"&gt; Hole: the circle follows the mouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6v78i80xw55t2ehinnr.webp" alt="Cut by line" width="800" height="450"&gt; Cut: drag a line across&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6mf8s87wpi2rkk1ykl6e.webp" alt="Measure" width="800" height="450"&gt; Measure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhvryvv0inet0uidx1l2i.webp" alt="Size" width="800" height="450"&gt; Size: drag, or type&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyf7dyrbj2lfhkjt12jza.webp" alt="Numbers" width="800" height="450"&gt; The Numbers pop-up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6odf803qxg3babrpdr3l.webp" alt="Imported" width="800" height="450"&gt; A 5200 mm STL after Open: shrunk to fit, centred&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F28plbgzzf6jfu6sc6x97.webp" alt="Open" width="800" height="450"&gt; Open: choose the file's units here&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why the tools are like this, and which existing add-ons and programs they were modelled on: &lt;a href="https://github.com/The-Dorkknight/simplemesh/blob/main/docs/DESIGN.md" rel="noopener noreferrer"&gt;docs/DESIGN.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is tested and what is not
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tested in a real Blender 4.0.2 window&lt;/strong&gt; (virtual display, simulated mouse and keys, 114 checks, &lt;code&gt;tests/gui_session.py&lt;/code&gt;; the first part drives the tools through the thin strip with the panel off, the last part drives the panel): &lt;strong&gt;Easy mode as a scripted child's session&lt;/strong&gt;: Blender starts in it; Block is picked up and put down; a Ball is put on top of the Block and lands on it; clicking selects; pulling dots makes the block taller, bigger and spins it; dragging slides it; colour, Copy, Bin; a loop drawn with the mouse becomes a solid; Ideas places a rocket; Glue makes it one closed model; Undo; the arrow buttons and right-drag turn the view; the Clay button and Done; Print! glues, repairs, sits everything on the bed and opens the save box; a saved project reopens in Easy mode. Then the strip tools: start-up tidy (one 3D view, millimetres, two workspace tabs); import of a 5200 mm STL lands centred and sensibly sized on screen; view re-centres after scaling; &lt;strong&gt;F&lt;/strong&gt; finds a model 4.7 m away; every strip tool by click/drag/keys (lay flat by hover+click, move by drag and arrow key, turn by drag and by key, size by typing and by drag, cut by click and by dragging a line, pull a face by typing and by dragging, hole with typed diameter, measure); Clay: entering from the strip, millimetre brush size, a Pull stroke stretching a flat face from 20 to 39 mm, switching to Push in, Ctrl+Z after the first stroke staying in Clay and taking back just that stroke, redo, Done merging flat areas and leaving the model watertight; Sculpt Mode entered the way Blender's own mode menu does it being noticed, set up automatically (small triangles, automatic detail, the Pull brush in millimetres) and a drag then pulling the block from 20 to 39 mm; undo of a tool step; switching tools from the strip while a tool is on; the four pop-ups and the Open file chooser appear. &lt;strong&gt;The panel&lt;/strong&gt;: its mouse watcher starts by itself; a click on the Lay flat key starts the tool, lights the key and turns the red button into FINISH LAY FLAT; the red button finishes it; clicks on the panel do not reach the model behind it; the red button starts Clay and the keys become brushes; Brush +, a brush key, a stroke in the view, the Undo key (the stroke goes, Clay stays), DONE; the Luminous key; Hide clutter and back; the Easy key, a picture button under the panel, a panel tool started from Easy mode and Easy mode taking over again afterwards; a recovery copy written while the panel is watching the mouse. &lt;strong&gt;The installer window&lt;/strong&gt; was driven the same way (&lt;code&gt;tests/test_installer_gui.py&lt;/code&gt;, 20 checks): it found and questioned two Blenders, ticking and unticking rows, refusing a program that is not Blender, Install, the two options, Remove, and Open Blender all worked, and Blender then started normally with the add-on on. Plain &lt;code&gt;python3 install.py&lt;/code&gt; and the Linux launcher script both opened the window (&lt;code&gt;tests/test_install_launch.py&lt;/code&gt;, 14 checks, starts &lt;code&gt;install.py&lt;/code&gt; as its own program and looks for its window). With tkinter switched off, the dialog-box version (zenity) was clicked through an install by hand-placed mouse clicks, and the terminal version picked Blender number 1 from its list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also tried by hand with real mouse and keyboard events&lt;/strong&gt; (sent through the display server with &lt;code&gt;xdotool&lt;/code&gt;, not Blender's own event simulation; Blender 4.0.2 started normally after the installer; these sessions are not part of the repository's tests): placing a block and pulling a dot; opening a 234-triangle bracket and a 327,680-triangle model through the real file chooser; Lay flat on the bracket; a long Clay Pull stroke on both; &lt;strong&gt;Sculpt Mode chosen from Blender's mode menu&lt;/strong&gt; (in 0.4.2 two long drags changed nothing you could see, in 0.4.3 the set-up runs and the drag pulls a horn out); Ctrl+Z after one and after two Clay strokes; Undo pressed until nothing was left, then adding a shape; Edit Mode and &lt;em&gt;Back to the models&lt;/em&gt;; &lt;strong&gt;Report a problem&lt;/strong&gt; (which caught a real error in the first version of the undo repair). With &lt;strong&gt;Blender 5.0.1&lt;/strong&gt; and version 0.4.2, placing a block, pulling a dot and a Clay Pull stroke worked the same way; nothing later than 0.4.2 was run in a 5.x window. &lt;strong&gt;For 0.5.0, on a 1366 x 768 screen&lt;/strong&gt;: the panel's keys by real clicks (Lay flat and FINISH, MOULD LIKE CLAY, Brush + three times, Build up, strokes, the Undo key, DONE, Luminous, Hide clutter, More tools and its Exact numbers pop-up, Report a problem); four fast clicks on a picture button all counting; an 82,000-triangle model opened through the file chooser and moulded with five strokes (Blender stayed under 0.9 GB and DONE took under 8 seconds on this machine's software rendering). &lt;strong&gt;The freeze&lt;/strong&gt;: with 0.4.3 the same model, five strokes and then &lt;em&gt;Finer everywhere&lt;/em&gt; left Blender not answering for several minutes at 4 GB; that button no longer exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tested without a window&lt;/strong&gt; (&lt;code&gt;tests/test_headless.py&lt;/code&gt;, Blender &lt;strong&gt;5.2.2&lt;/strong&gt; as the &lt;code&gt;bpy&lt;/code&gt; module, also with numpy disabled): all the mesh work behind the tools: import sizes and units, scaling, lay flat, every Easy shape is closed and the right size; hand-drawn outlines (clean loop, overshoot, figure-of-eight, scribble) always give a valid solid; words become closed letters; each of the five Ideas goes through Print! to one watertight model that fits the bed; glue with holes; cuts with any plane (volumes add up, caps watertight), pulling faces, refining for Clay and merging flat areas again, Clay set-up including all seven brushes on Blender 5.2's brush system, drilling through and blind holes, hole shapes, weld, separate, repair, simplify, export and re-import.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not tested:&lt;/strong&gt; any real person using it, and in particular &lt;strong&gt;no child has tried Easy mode&lt;/strong&gt;: whether a ten-year-old finds the dots, the stacking and the wording obvious is exactly what a scripted mouse cannot tell me; the Words and Save pop-up boxes themselves (the operators behind them were run); screens smaller than 1366 x 768 (the picture buttons were looked at on 1600 x 900 and 1366 x 768 only); trackpads; the two reported problems on the computer where they were reported: "a drag only changes it slightly" (0.4.3: reproduced here by choosing Sculpt Mode from Blender's own menu) and "on sculpt all controls lock up" (0.5.0: reproduced here with &lt;em&gt;Finer everywhere&lt;/em&gt;), so in both cases I fixed something that matches the description without being able to check that it is what happened there; &lt;strong&gt;the panel, the look keys and every other 0.4.3 and 0.5.0 change in a window on any Blender other than 4.0.2&lt;/strong&gt; (on 5.2 only the windowless tests, where the panel is laid out and its buttons run but nothing is drawn or clicked); Blender 3.x and 4.1 to 4.5 in any form; the panel on high-resolution (Retina / 4K scaled) screens, on two monitors or in more than one Blender window; the recovery copy over a real working session (it was written once on demand and once by its timer); Clay on models above about 330,000 triangles and on slow computers; the dense-model path above 700,000 triangles; large real-world STLs (hundreds of thousands of triangles: hovering builds a lookup of the model first, which may pause for a few seconds); the full scripted window session on Blender 3.x, 4.2+ or 5.x (only the windowless tests ran on 5.2); a real double-click on &lt;code&gt;install.py&lt;/code&gt; or the launcher in a desktop file manager (they were started from a command line on a virtual display; the 0.4.2 fix for "&lt;code&gt;install.py&lt;/code&gt; shows nothing" was not confirmed on the computer where it was reported); macOS and Windows, including the Mac and Windows double-click launchers and the "Add another Blender..." file chooser box itself; flatpak/snap/Steam Blender with the installer (the search for them is written but there was none here to find); the extension-format zip; the GitHub workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  More
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/The-Dorkknight/simplemesh/blob/main/docs/DESIGN.md" rel="noopener noreferrer"&gt;docs/DESIGN.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/simplemesh/blob/main/HOW_IT_WORKS.md" rel="noopener noreferrer"&gt;HOW_IT_WORKS.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/simplemesh/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/simplemesh/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt; · &lt;a href="https://github.com/The-Dorkknight/simplemesh/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/The-Dorkknight/simplemesh" rel="noopener noreferrer"&gt;The-Dorkknight/simplemesh on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>opensource</category>
      <category>software</category>
    </item>
    <item>
      <title>Pi Hotspot Injector</title>
      <dc:creator>Matthew Armstrong</dc:creator>
      <pubDate>Sun, 11 Oct 2026 08:38:05 +0000</pubDate>
      <link>https://dev.to/open-madscience/pi-hotspot-injector-3h51</link>
      <guid>https://dev.to/open-madscience/pi-hotspot-injector-3h51</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FThe-Dorkknight%2Fpi-hotspot-injector%2Fmain%2Fdocs%2Fimages%2Ficon-512.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FThe-Dorkknight%2Fpi-hotspot-injector%2Fmain%2Fdocs%2Fimages%2Ficon-512.png" alt="Pi Hotspot Injector icon" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source-available · Non-commercial&lt;/strong&gt; · Linux desktop · Pi 1 to Pi 5 · &lt;a href="https://The-Dorkknight.github.io/pi-hotspot-injector/" rel="noopener noreferrer"&gt;Project page&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Licence:&lt;/strong&gt; free for &lt;strong&gt;non-commercial&lt;/strong&gt; use (personal, hobby, educational, charity). Provided &lt;strong&gt;as is, with no warranty and no liability&lt;/strong&gt;. See &lt;a href="https://github.com/The-Dorkknight/pi-hotspot-injector/blob/main/LICENSE.md" rel="noopener noreferrer"&gt;LICENSE.md&lt;/a&gt; (PolyForm Noncommercial 1.0.0). Use it at your own risk: it can erase the wrong drive if you pick the wrong one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A point-and-click Linux tool that turns a blank SD card into a &lt;strong&gt;Klipper 3D-printer controller that broadcasts its own Wi-Fi hotspot&lt;/strong&gt;. It's handy in a workshop or garage with no Wi-Fi network around: power the Pi on, join its Wi-Fi from your phone or laptop, and open Mainsail in a browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One card works in every Raspberry Pi&lt;/strong&gt;, from the original Pi 1 and Pi Zero W up to the Pi 4, 400 and 5.&lt;/p&gt;

&lt;p&gt;It does everything from your Linux PC, with no need to boot the Pi first or plug in a keyboard and screen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write the OS image&lt;/strong&gt; (Raspberry Pi OS Lite, or MainsailOS) to an SD card or USB stick, straight from the downloaded &lt;code&gt;.img.xz&lt;/code&gt; / &lt;code&gt;.zip&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Klipper&lt;/strong&gt;: install Klipper, Moonraker and Mainsail (plus Crowsnest if you want a webcam, and the experimental &lt;a href="https://github.com/The-Dorkknight/klipplug" rel="noopener noreferrer"&gt;KlipPlug&lt;/a&gt; plugin manager if you want plugins) onto a clean Raspberry Pi OS Lite card. Everything comes clean and straight from each project, compiled so it runs on every Pi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up the hotspot&lt;/strong&gt;: Wi-Fi name, password, country and IP address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clone &amp;amp; Backup&lt;/strong&gt; (optional): save a finished card as an image file, and copy it onto more cards, each one becoming a separate Pi with its own name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extras&lt;/strong&gt;: a Pi Zero / Pi 1 compatibility check-and-fix for existing cards, the &lt;strong&gt;Card Inquisitor&lt;/strong&gt; boot diagnostics, and a standalone hostapd + dnsmasq installer.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Write the image&lt;/th&gt;
&lt;th&gt;Build Klipper&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpwnhpl7zq0x7xksb0m9.png" alt="Write Image tab" width="700" height="688"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ufxjvhp3u8okre6dlog.png" alt="Build Klipper tab" width="700" height="860"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Set up the hotspot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Clone &amp;amp; Backup&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzpir2v0cvri0g2g93koi.png" alt="Hotspot Setup tab" width="700" height="688"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1il7whlbafv1jp8akbtg.png" alt="Clone &amp;amp; Backup tab" width="700" height="780"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sanity check 1 of 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Sanity check 2 of 2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F88sjgrlfl80v7hoekk01.png" alt="Sanity check 1" width="700" height="688"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb2acu3dm7cw8gl0ht31g.png" alt="Sanity check 2" width="700" height="688"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚠️ Read this first
&lt;/h2&gt;

&lt;p&gt;This tool writes directly to disks. Used on the wrong drive, it &lt;strong&gt;permanently erases&lt;/strong&gt; that drive's data. It has several safety guards (below), but &lt;strong&gt;back up anything important&lt;/strong&gt; before you use it, and double-check which drive you pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  More documentation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/pi-hotspot-injector/blob/main/HOW_IT_WORKS.md" rel="noopener noreferrer"&gt;HOW_IT_WORKS.md&lt;/a&gt;: what the tool does to your card and PC, step by step&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/pi-hotspot-injector/blob/main/RESEARCH_NOTES.md" rel="noopener noreferrer"&gt;RESEARCH_NOTES.md&lt;/a&gt;: what was found out along the way, and what is and isn't tested on real hardware&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/pi-hotspot-injector/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;: what changed in each version&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/pi-hotspot-injector/blob/main/THIRD_PARTY.md" rel="noopener noreferrer"&gt;THIRD_PARTY.md&lt;/a&gt;: the software it installs, and their licences&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://The-Dorkknight.github.io/pi-hotspot-injector/" rel="noopener noreferrer"&gt;Project page&lt;/a&gt; (GitHub Pages)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/The-Dorkknight/pi-hotspot-injector/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt; and &lt;a href="https://github.com/The-Dorkknight/pi-hotspot-injector/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt;: reporting problems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Double-click &lt;code&gt;PiInjector.desktop&lt;/code&gt;.&lt;/strong&gt; The first time, your file manager may ask whether you trust it. That's a one-off security check you have to click through:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Desktop&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux Mint (Cinnamon / Nemo)&lt;/td&gt;
&lt;td&gt;Click &lt;em&gt;Mark as Trusted&lt;/em&gt; (or &lt;em&gt;Launch Anyway&lt;/em&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KDE (Dolphin)&lt;/td&gt;
&lt;td&gt;Click &lt;em&gt;Continue&lt;/em&gt; / &lt;em&gt;Execute&lt;/em&gt; if asked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu (GNOME Files)&lt;/td&gt;
&lt;td&gt;GNOME Files won't run launchers from ordinary folders. Start it once from a terminal (below), then use &lt;strong&gt;Extras → Add to applications menu&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then enter your password when asked. The tool needs administrator rights to write to SD cards, and it asks for them itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Or&lt;/strong&gt;, from a terminal: &lt;code&gt;python3 pi_injector.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once it's running, &lt;strong&gt;Extras → Add to applications menu&lt;/strong&gt; puts it in your start menu like any other app.&lt;/p&gt;

&lt;p&gt;On startup, a short disclaimer appears that you must accept before you can use the tool. Reading the full licence from that screen is optional.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FThe-Dorkknight%2Fpi-hotspot-injector%2Fmain%2Fdocs%2Fimages%2Fdisclaimer.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FThe-Dorkknight%2Fpi-hotspot-injector%2Fmain%2Fdocs%2Fimages%2Fdisclaimer.png" alt="Start-up disclaimer" width="588" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Write Image
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;Download Raspberry Pi OS Lite (32-bit)&lt;/strong&gt;. It fetches the official image (about 530 MB) from raspberrypi.com, checks it against their published checksum, and fills it in for you. Flaky Wi-Fi is fine: progress is saved as it goes (a checkpoint every 10 MB, plus wherever the connection drops), and it reconnects by itself and carries on from there. Cancel just pauses it; press Download again later, even after a restart, to continue. (Downloading it yourself is fine too: it's the &lt;code&gt;…-raspios-…-armhf-lite.img.xz&lt;/code&gt; file. &lt;strong&gt;Not&lt;/strong&gt; "Raspberry Pi Desktop for PC and Mac", which is a PC ISO that can't run on a Pi.) MainsailOS works too; then skip step 2.&lt;/li&gt;
&lt;li&gt;Plug in your SD card (in a USB reader) or USB stick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browse...&lt;/strong&gt; to the image, pick the drive, then &lt;strong&gt;Write Image to Drive&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Get past the &lt;strong&gt;two sanity checks&lt;/strong&gt; (below). Writing and verifying take a few minutes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Build Klipper
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Leave the freshly written card plugged in. &lt;strong&gt;Don't boot it in a Pi first&lt;/strong&gt;, because the build needs a never-started card.&lt;/li&gt;
&lt;li&gt;Choose a login user (default &lt;code&gt;pi&lt;/code&gt;), a password (&lt;strong&gt;write it down&lt;/strong&gt;) and a hostname (give each printer its own).&lt;/li&gt;
&lt;li&gt;Options: &lt;strong&gt;Printer firmware build tools&lt;/strong&gt; (on by default; lets you compile and flash your printer board's firmware on the Pi, as most guides do; uses about 1 GB), &lt;strong&gt;Crowsnest&lt;/strong&gt; (webcam support) and &lt;strong&gt;KlipPlug&lt;/strong&gt; (plugin manager, experimental; see below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Klipper Card&lt;/strong&gt;. It takes about 1–2 hours and needs this PC online, so keep the PC awake. What it does:

&lt;ul&gt;
&lt;li&gt;enlarges the card's system partition (as the Pi would on its first start);&lt;/li&gt;
&lt;li&gt;sets up the user, SSH and hostname;&lt;/li&gt;
&lt;li&gt;installs Klipper, Moonraker, Mainsail, nginx and (optionally) Crowsnest, straight from each project;&lt;/li&gt;
&lt;li&gt;on a 32-bit card, compiles everything for the oldest Pi and checks every compiled file;&lt;/li&gt;
&lt;li&gt;if you ticked KlipPlug, installs it and runs it once on the card's own Python;&lt;/li&gt;
&lt;li&gt;finally &lt;strong&gt;test-starts Moonraker&lt;/strong&gt; on an emulated Pi to prove it works.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cancel is safe. A stopped or failed build can simply be started again, and it carries on where it left off.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After the first start, Mainsail will show a Klipper error until you put your printer's configuration into &lt;code&gt;printer.cfg&lt;/code&gt;. Klipper's example configs are in &lt;code&gt;~/klipper/config/&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  The KlipPlug option (experimental)
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/The-Dorkknight/klipplug" rel="noopener noreferrer"&gt;KlipPlug&lt;/a&gt; is a separate project by the same author: a plugin manager for Klipper that can run many OctoPrint plugins. Ticking the box adds it to the card as its own service; its page is at &lt;code&gt;http://&amp;lt;hostname&amp;gt;.local:7130&lt;/code&gt; (on the hotspot: &lt;code&gt;http://192.168.50.1:7130&lt;/code&gt;), next to Mainsail, which is not changed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Where it comes from:&lt;/strong&gt; if a &lt;code&gt;klipplug&lt;/code&gt; folder or a &lt;code&gt;klipplug….zip&lt;/code&gt; sits next to &lt;code&gt;pi_injector.py&lt;/code&gt;, that copy is used. Otherwise it is downloaded from GitHub. Use the local copy for a build without that download, or for a version that isn't on GitHub. A local copy goes onto the card without its &lt;code&gt;.git&lt;/code&gt; folder (a clone's settings can hold a login token), so Mainsail's update panel won't list it; a copy downloaded from GitHub is listed there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If it can't be installed&lt;/strong&gt; (no download, a broken zip), the build carries on and finishes without it, and tells you so at the end. The Klipper card is complete either way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What has and hasn't been tried:&lt;/strong&gt; this option was tested with a stand-in for the card on a PC (the same install step, run in a chroot, then the installed service started against Klipper and Moonraker). It has &lt;strong&gt;not&lt;/strong&gt; been built onto a real SD card or started on a real Pi yet. KlipPlug itself is work in progress and has not been run on a physical printer. A Pi Zero W has little memory (512 MB); how well KlipPlug runs there beside Klipper is not measured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Licence:&lt;/strong&gt; KlipPlug is under AGPL-3.0, not this tool's licence. See &lt;a href="https://github.com/The-Dorkknight/pi-hotspot-injector/blob/main/THIRD_PARTY.md" rel="noopener noreferrer"&gt;THIRD_PARTY.md&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Hotspot Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Leave the card plugged in (or insert a card flashed with another tool).&lt;/li&gt;
&lt;li&gt;Check the settings. A random Wi-Fi password is generated for you: &lt;strong&gt;write it down&lt;/strong&gt;. Set the country code to where the Pi will be used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inject Hotspot&lt;/strong&gt;. If the card lacks hostapd/dnsmasq, the tool offers to install them (this PC needs internet access; it takes 5–15 min).&lt;/li&gt;
&lt;li&gt;Note the Wi-Fi name, password and web address it shows you.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4. Use it
&lt;/h3&gt;

&lt;p&gt;Put the card in the Pi and power it on. &lt;strong&gt;The first boot takes a few minutes&lt;/strong&gt; because the Pi sets itself up and may restart once. Then join the Wi-Fi network and open &lt;code&gt;http://192.168.50.1&lt;/code&gt; (or whatever address you chose). On a laptop, &lt;code&gt;http://&amp;lt;hostname&amp;gt;.local&lt;/code&gt; works too (e.g. &lt;code&gt;http://klipper.local&lt;/code&gt;; the hostname is the one set in step 2, not the login user name). Phones often can't open &lt;code&gt;.local&lt;/code&gt; names, so use the number there.&lt;/p&gt;

&lt;p&gt;If your phone has joined a hotspot with the same name before, choose &lt;strong&gt;Forget network&lt;/strong&gt; on it first. Otherwise it may quietly try the old saved password.&lt;/p&gt;

&lt;p&gt;Running more than one of these hotspots at once? Give each card a different &lt;strong&gt;Hotspot address&lt;/strong&gt; number (or press &lt;em&gt;Random&lt;/em&gt;), so they don't clash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clone &amp;amp; Backup (several Pis)
&lt;/h2&gt;

&lt;p&gt;Setting up several Pis? Build &lt;strong&gt;one&lt;/strong&gt; card with steps 1–3, then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A. Save a card as an image file&lt;/strong&gt;: pick the finished card; it is only read, never changed. With &lt;strong&gt;Shrink&lt;/strong&gt; ticked (recommended) the file is only as big as what's on the card, a few GB, so it fits any card big enough. That matters because a "32 GB" card from another brand is often slightly smaller than yours. Optionally compress it to &lt;code&gt;.img.gz&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B. Write a copy onto another card&lt;/strong&gt;: pick the image and a blank card (the same two safety checks as tab 1). The copy is grown to fill the card and, with &lt;strong&gt;Make it a separate Pi&lt;/strong&gt; ticked, gets its own name (&lt;code&gt;klipper2&lt;/code&gt;, &lt;code&gt;klipper3&lt;/code&gt;…), a fresh machine ID and new SSH keys, so two Pis never get mixed up on a network.&lt;/li&gt;
&lt;li&gt;Run &lt;strong&gt;step 3&lt;/strong&gt; on the copy to give it its own hotspot name and password.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep the image file as a backup: if a card ever dies, write it again in a few minutes. To restore onto the &lt;em&gt;same&lt;/em&gt; Pi, untick &lt;strong&gt;Make it a separate Pi&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Pi?
&lt;/h2&gt;

&lt;p&gt;A card built from &lt;strong&gt;Raspberry Pi OS Lite (32-bit)&lt;/strong&gt; runs in any of these:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pi&lt;/th&gt;
&lt;th&gt;Works&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pi 1 A/B, Zero, Zero W&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Everything is compiled for these (ARMv6). Slow, but fine for one printer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero 2 W, Pi 2, Pi 3&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pi 4, Pi 400&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Best choice for printing plus a webcam.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pi 5&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Runs the 32-bit card fine. For a Pi 3, 4 or 5 only, the 64-bit Lite image also works.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The build emulates the Pi Zero's own processor while it compiles, so nothing ends up built for a newer chip than the oldest Pi's. Every newer Pi runs that code natively. That's also why cards built with this tool don't hit the Moonraker &lt;em&gt;Illegal instruction&lt;/em&gt; crash that affects some ready-made images on a Pi Zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Size window:&lt;/strong&gt; only removable drives sold as &lt;strong&gt;8–32 GB&lt;/strong&gt; (±1 GB tolerance) are ever offered as write targets. External hard drives, SSDs, 64 GB+ sticks and tiny drives are refused outright.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal disks are never offered:&lt;/strong&gt; SATA/NVMe drives, built-in eMMC storage, the live-USB stick you booted from, and anything holding this PC's system, home folders or swap are excluded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two sanity checks before erasing:&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;A summary of the drive, what's on it now (partition names), and the image that will replace it. &lt;strong&gt;No&lt;/strong&gt; is the default button.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Are you reeeeley sure you want to overwrite disk …?"&lt;/em&gt; You must &lt;strong&gt;type the drive's name&lt;/strong&gt; (e.g. &lt;code&gt;sdb&lt;/code&gt;) to enable the button.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-checks the drive right before writing.&lt;/strong&gt; If it was unplugged or swapped for another one with the same name, nothing is written.&lt;/li&gt;
&lt;li&gt;Refuses to write an image file that's &lt;strong&gt;stored on the drive being overwritten&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The hotspot, diagnostics and package tools only touch a card that really is a Raspberry Pi OS card (checked read-only first).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Klipper&lt;/strong&gt; checks read-only first that it's a never-started Raspberry Pi OS card, that the user name doesn't clash with a system account, and that the filesystem checks clean with this PC's tools. Only then does it change anything. It never touches a card that's already been used.&lt;/li&gt;
&lt;li&gt;Checks that the image &lt;strong&gt;fits&lt;/strong&gt;, and warns if the file doesn't look like a bootable disk image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclusive access:&lt;/strong&gt; the kernel blocks the write if anything still has the drive open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify after writing&lt;/strong&gt; (on by default) reads the drive back and compares checksums. This catches faulty and fake-capacity cards.&lt;/li&gt;
&lt;li&gt;Won't let you close the window mid-job without a warning. &lt;strong&gt;Cancel&lt;/strong&gt; works during writes and package installs, and the card is tidied up (temporary mounts and files removed) even if the tool is closed or killed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Why isn't my drive listed?"&lt;/strong&gt; explains exactly why each drive was excluded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The size window is set by &lt;code&gt;MIN_WRITE_TARGET_BYTES&lt;/code&gt; / &lt;code&gt;MAX_WRITE_TARGET_BYTES&lt;/code&gt; near the top of &lt;code&gt;pi_injector.py&lt;/code&gt;. Change it at your own risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compatibility notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Target images: Raspberry Pi OS / MainsailOS based on &lt;strong&gt;Bookworm or newer&lt;/strong&gt; (NetworkManager-based).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;hostapd + dnsmasq&lt;/strong&gt; method is recommended. NetworkManager's own AP mode is unreliable on the Pi Zero W's BCM43430 Wi-Fi chip on trixie-based images.&lt;/li&gt;
&lt;li&gt;For cards not built with this tool, the optional login user from &lt;em&gt;Hotspot Setup&lt;/em&gt; is created via &lt;code&gt;userconf.txt&lt;/code&gt; on the boot partition. (A built card already has its user.)&lt;/li&gt;
&lt;li&gt;Built from Raspberry Pi OS &lt;strong&gt;with desktop&lt;/strong&gt; instead of Lite? That works too. The card is set to start without the graphical desktop, to leave memory for Klipper (switch it back on with &lt;code&gt;sudo raspi-config&lt;/code&gt; → System Options → Boot).&lt;/li&gt;
&lt;li&gt;A built card has cloud-init and Raspberry Pi OS's first-boot "create a user" wizard switched off. The build has already set up the user, SSH and hostname, and those first-boot tools would otherwise rename the user and break Klipper's paths.&lt;/li&gt;
&lt;li&gt;After you run updates &lt;em&gt;on the Pi itself&lt;/em&gt;, pip picks packages for that Pi's own processor. If you later move such a card from a Pi 3/4/5 into a Pi Zero / Pi 1, run &lt;strong&gt;Extras → Check card&lt;/strong&gt; first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pi Zero, Zero W and Pi 1 (ARMv6):&lt;/strong&gt; some recent images (e.g. MainsailOS 3.x, whose build moved to ARMv7) include compiled software these older boards can't run, so Moonraker crash-loops with &lt;em&gt;Illegal instruction&lt;/em&gt;. &lt;em&gt;Hotspot Setup&lt;/em&gt; warns you when it spots this, and &lt;strong&gt;Extras → Fix for Pi Zero / Pi 1&lt;/strong&gt; rebuilds the affected Python packages from source for ARMv6, then tests each one on an emulated Pi Zero CPU. The Pi Zero 2 W, 3, 4 and 5 aren't affected.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hardware</category>
      <category>linux</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
