The draft taper: a real Minkowski sum

Every struck character needs a draft angle - the print face is narrower than the root that embeds it in the type element body, like a rivet. v4 builds this by taking the flat, triangulated glyph outline and computing a real Minkowski sum (manifold3d.Manifold.minkowski_sum) against a cone. A Minkowski sum can’t produce a self-intersecting result on any input topology, which is the whole reason it replaced v4’s first approach: a hand-rolled per-vertex outline offset (adapted from a friend’s 2023 “TypeCylinder” tool) that folded through itself on narrow glyph features - 71 of 84 characters failed a shapely simplicity check under that scheme. OpenSCAD’s own minkowski() was also ruled out, for being too slow at production scale.

Real booleans, not concatenation

Assembling the type element - characters onto the cylinder, the platen cutout, the final Additive - Subtractive - goes through real manifold3d boolean operations (sp.union_all() / Manifold.batch_boolean()), never trimesh.util.concatenate(). Concatenation just merges vertex/face arrays with no boolean resolution at all - wherever two surfaces overlapped, both stayed fully intact and superimposed, with no new edge at the actual intersection. This shipped as a real bug once, caught via a 1148mm³ double-counted-overlap volume discrepancy.

Adaptive glyph outline sampling

Glyph outlines are sampled with recursive de Casteljau Bezier subdivision to a flatness tolerance in mm, not a fixed points-per-mm rate. Straight strokes get zero extra subdivision (they’re already flat); curves get exactly as many points as their own curvature needs. The old fixed-rate scheme left 46-71% of a straight-stroke glyph’s points geometrically redundant - and since Minkowski cost scales with the product of the two operands’ face counts, that redundancy directly inflated build time. Switching to adaptive sampling measured a 1.3x-4.5x real build-time reduction with no correctness loss (volumes matched the old output to within 0.03-1% across both quadratic/TrueType and cubic/CFF fonts).

Curvature is applied before the sum, never after

Curvature or warp on the base solid (the platen cutout, for example) is applied to the flat shape before the Minkowski sum runs, never patched onto the already-swept result afterward. A draft angle is only valid for the exact shape it was summed with; patching a curve on after the fact leaves the walls built as if the tip were still flat.

Real machine numbers live in config, not code

Every physical dimension, tolerance, offset, and facet-count/resolution constant lives in that machine’s config/*.yaml, never hardcoded in Python. This holds even for numbers that feel like implementation details (circle segments, revolve sections) - if a facet count needs tuning for a real reason, it’s a new knob in the config’s quality: section, the same as every other one.

No manual help needed for the platen cut

An early investigation suspected the real platen boolean needed manual Y-breakpoint insertion to cut cleanly along a long, adaptively unsubdivided straight edge (a stem on ‘d’/’l’/’k’, for example). A breakpoint-insertion mechanism was built, found to introduce its own defect (a fan of degenerate triangles wherever a breakpoint-dense edge met an already-dense curve region), and removed again once a properly size-normalized check confirmed the original concern was a false positive - 0 faces exceeding 1.5x a character’s own bounding-box diagonal, across every character in both the cylinder and spherical glyph families. The real boolean subdivides a stem’s wall finely wherever the platen curve actually changes, and leaves it as one larger (but still correct) facet wherever it doesn’t - for free, with no pre-conditioning of the input contour.