v4: Minkowski-draft type element pipeline

Generates struck-character type elements - typewheels, shuttles, typeballs, slugs - for 15 antique typewriters, as watertight solids ready to print in resin. Each machine is driven by its own config YAML, with an interactive TUI for editing and previewing it.

The draft taper (the cone-shaped widening from a character’s print face down to its embedded root) is a real Minkowski sum (manifold3d.Manifold.minkowski_sum) between the flat glyph shape and a cone - not OpenSCAD’s minkowski(), which is far too slow at this scale, and not a per-vertex outline offset, which can silently fold through itself on narrow features. Every assembly step is a real manifold3d boolean too.

Machines: Blickensderfer, Postal, Mignon, Bennett, Helios Klimax, Hammond, Hammond Split, Selectric I/II, Selectric III, Selectric Composer, Type Slug, Vogue Slug, Gauge Slug, Oliver Slug, Lumi Slug.

Documentation

This README covers setup, usage and the file layout. Everything else lives in its own document so this one stays short:

Document What’s in it
PIPELINE.md The glyph pipeline end to end - contour tracing, platen cutout, the Minkowski sweep, mirroring, alignment, performance
MACHINES.md Per-machine architecture, what each shares with the others, element assembly, resin supports, calibration
TUNER.md tune.py - the interactive tuner and its config tiers
LAYOUT_TRANSCRIPTION.md How the keyboard layouts were transcribed from real manufacturer type catalogs, and what’s still un-transcribed
CATALOG_INDEX.md Every catalogued Hammond and Blickensderfer shuttle, with its import status
LIMITATIONS.md Known limitations, and resolved ones worth remembering
CLAUDE.md Conventions and hard-won invariants - read before changing geometry code
SESSION_LOG.md Chronological development history and the current resume point

Rendered at type-elements.leonardchau.com. The project story - collaborators, real prints, the machines themselves - is at leonardchau.com/projects/type-elements.

License

Dual-licensed, with the license files at the repository root:

  • Code (*.py, lib/**, scripts) - GPL-3.0-or-later. Fork it, change it, keep it open.
  • Design data (config/**, generated STLs, renders, these .md files) - CC BY-NC-SA 4.0. Print your own freely; commercial sale is reserved to the copyright holder.

NOTICE has the exact file-by-file scope, attribution, the third-party dependency list, and the font caveat. One rule worth knowing before you move files around: lib/layouts/** is pure layout data but stays under the GPL, because tune.py imports it directly and a non-commercial license inside an imported module would make the combined program undistributable.

Setup

Linux/macOS:

cd v4
./lin_setup.sh      # creates .venv, installs requirements.txt
./lin_start.sh      # launches tune.py

Windows:

cd v4
win_setup.bat
win_start.bat

Either script is equivalent to the manual python -m venv .venv / pip install -r requirements.txt steps, just idempotent (safe to re-run, e.g. after requirements.txt changes) and consistent across platforms. tune.py shells out to f3d for the live preview window - lib/f3d_bootstrap.py finds one already on PATH if installed, or downloads a pinned build automatically the first time it’s needed (see PACKAGING_PLAN.md), so no separate f3d install step is required on either platform.

One first-run note: the shipped configs’ font.path values are absolute paths into the font library on the machine they were tuned on, so a fresh checkout elsewhere (Windows especially) needs them re-pointed before a build will find its font. The Font & Alignment tab’s Installed button does that in one click - see TUNER.md.

Usage

.venv/bin/python3 generate.py config/blickensderfer.yaml
.venv/bin/python3 generate.py config/blickensderfer.yaml --flatness-tolerance-mm 0.002 --separation-mm 1.5
.venv/bin/python3 generate.py config/blickensderfer.yaml --no-core-groove   # skip the slow twisted grooves
.venv/bin/python3 generate.py config/blickensderfer.yaml --resin-support    # add ResinPrint()'s support rods
.venv/bin/python3 generate.py config/blickensderfer.yaml --out /tmp/test.stl
.venv/bin/python3 generate.py config/blickensderfer.yaml --flatness-tolerance-mm 0.05 --cone-segments 12   # faster iteration

.venv/bin/python3 rather than bare python3 - trimesh/manifold3d and the rest of the build stack are only installed in the venv Setup creates (or activate it first). --flatness-tolerance-mm is the glyph outline’s flatness budget in mm: SMALLER is finer and slower (config default 0.005), larger is coarser and faster. It replaced the old fixed-rate --points-per-mm, which no longer exists.

All real-machine numbers live in the config file, not in code. A second machine is mostly just a new YAML file under config/ - machine: postal in config/postal.yaml tells generate.py/export_glyphs.py which Python module to import (see MACHINES.md). See config/blickensderfer.yaml for the full parameter set; every value there is commented with which v2/blickensderfer.scad (or lib/core_shaft.scad / lib/resin_support.scad) variable it corresponds to.

Layout

generate.py                 entry point - loads a config, builds, exports
tune.py                     interactive TUI for editing the config (see above)
type_test.py                flat CPI/LPI-spaced text preview used by tune.py's Type Test tab
export_glyphs.py            exports every configured character to its own STL, for visual inspection
font_coverage.py            scans a font library for glyph coverage against a layout/config/string
generate_legend.py          keyboard-legend sheet renderer
generate_supports.py        standalone resin-support generator
generate_thumbnails.py      batch thumbnail renders
config/
  <machine>.yaml             every real machine parameter + build/alignment settings, one per machine
  <machine>.running.yaml     gitignored scratch copy tune.py actually edits/saves (see above)
                             15 machines: blickensderfer, postal, mignon, bennett, helios,
                             hammond, hammond_split, selectric12, selectric3,
                             selectric_composer, type_slug, vogue_slug, gauge_slug,
                             oliver_slug, lumi_slug
lib/
  glyph_poc.py               single-glyph mesh pipeline (the core technique)
  scad_primitives.py         revolve_polygon/extrude/transform helpers, generic (not machine-specific)
  build_log.py               the ONE progress/mesh-report/atomic-export format (see CLAUDE.md)
  resin_support.py           shared resin-support geometry
  svg_import.py              SVG path -> polygon import (logos/marks)
  system_fonts.py            lists the fonts installed on this machine (Linux/Windows) for
                             tune.py's font pickers - see TUNER.md
  f3d_bootstrap.py           finds or downloads the pinned f3d used by tune.py's preview
  contour_inspect.py         standalone contour diagnostic (not in the build path)
  heightfield_poc.py         abandoned height-field experiment, kept for reference; nothing imports it
  -- shared family modules --
  cylinder_machine.py        Blickensderfer/Postal shared code - see MACHINES.md
  spherical_machine.py       shared by the 3 IBM/Selectric machines
  wing_slug.py               shared by type_slug/vogue_slug/gauge_slug
  box_slug.py                shared by oliver_slug/lumi_slug
  -- one per machine --
  blickensderfer.py          configure() + drive-pin trio (HollowSpace/DrivePin/ResinSupport)
  postal.py                  configure() + drive-pin trio
  mignon.py                  configure() + full body/shaft/resin-support (see [`MACHINES.md`](/machines-detail/))
  bennett.py                 see [`MACHINES.md`](/machines-detail/)
  helios.py                  see [`MACHINES.md`](/machines-detail/)
  hammond.py / hammond_split.py
  selectric12.py / selectric3.py / selectric_composer.py
  type_slug.py / vogue_slug.py / gauge_slug.py / oliver_slug.py / lumi_slug.py
  hammond_legend.py, mignon_legend.py    keyboard-legend sheets for those machines
  layouts/                   ALL machines' keyboard/typeball layout presets - one
                             <machine>_layout.py each, aggregated by __init__.py.
                             tune.py imports from here and hardcodes no layout data.
                             See LAYOUT_TRANSCRIPTION.md for where the values came from.
output/
  <machine>_running.stl      latest generated result (scratch/working file, not a keeper - see tune.py's Save button)
  experiments/               diagnostic renders/sweeps from development (not regenerated by generate.py)
assets/                      SVG logos/marks (see svg_import.py)
docs/                        per-machine accessory docs (e.g. mignon_index_holder)

Machines documented in detail below: Blickensderfer/Postal (“Multiple machines”), Mignon, Bennett, Helios Klimax. The other ten - Hammond, Hammond Split, the three Selectrics and the five Type Slug machines - are ported and working but have no prose section here yet; their own module docstrings and CLAUDE.md’s “Porting a new machine” section carry that detail for now.

Accessories

Non-parametric companion parts that print alongside a machine’s type elements but aren’t generated by this pipeline (no config//lib/ counterpart) live under docs/. See docs/mignon_index_holder.md for the first one - a legend-card holder that goes with the Mignon element.