Living Hinge Generator — Operating Guide

Parametric lattice-hinge ("living hinge") SVG generator for laser cutting. Two files, which must sit in the same directory:

File Role
living-hinge-generator.js Library. require() it, call generate({...}) → {svg, stats, options}
living-hinge.js CLI front end (executable, #!/usr/bin/env node)

Requires Node ≥ 10.12. No dependencies, no install step.

The stock is 3mm Baltic birch plywood — that is what these are cut in, and what the defaults are aimed at. The tuning numbers are still starting points, not tested values: they are derived from geometry rather than measured off cut material, and bridge width in particular decides whether a hinge flexes or snaps. Cut a coupon (see Finding your real numbers) before committing a full panel.


Get the files

Released under CC0 1.0 — do what you like with them, no attribution needed. Built for LaserMadeMusic.


1. What it actually does

It emits an SVG containing a rectangular panel outline plus a field of cuts that let a rigid sheet bend along one axis (or two, for the biaxial patterns). It does not do nesting, tabs, joinery, or kerf compensation — it produces the hinge field and the panel rectangle, and you compose that into a larger part.

Everything is computed in millimetres internally. --units in only changes what gets written out.

Panel convention

Panel convention — width, length, margin, and the hinge field Drawn by panel-convention.js. The slits are real output from living-hinge-generator.js for a 60 x 84mm straight panel with a 7mm margin, so the figure matches what the tool makes. Proportions are illustrative; the shipped examples are 50 x 200mm. x = 0 x = width y = 0 y = length margin margin hinge field (rows of cutouts) width slits run ACROSS the width; the panel rolls ALONG the length

The slits are real generator output, not drawn by hand — see panel-convention.js. Proportions are illustrative; the shipped examples are 50 × 200mm.

The rule everything else serves: slits must reach x=0 and x=width. An uncut rail down either long edge stiffens the entire panel and it will not flex. The generator enforces this — it is why --side and --cell get resized (§6) and why the crosshatch edge cuts are exempt from --minSliver.


2. Quick start

cd living-hinge          # wherever you cloned or unzipped it

./living-hinge.js                                  # 50 × 200mm straight, to ./
./living-hinge.js -p wave -w 80 -l 300             # wave pattern, 80 × 300mm
./living-hinge.js -p dogbone -w 2in -l 10in        # inch input, mm output
./living-hinge.js -p chevron -w 3in --units in     # inch input AND inch output
./living-hinge.js --all --outdir ./hinges          # all 8 patterns at one size
./living-hinge.js --list                           # pattern names + their defaults
./living-hinge.js --help                           # full option list

Add --dry-run to any command to see the stats and the filename it would write without touching the disk. Use it constantly — it is instant and it is how you iterate.

./living-hinge.js -p dogbone --bridge 2.5 --hole 0.4 --dry-run
# dogbone  slit=10.625  bridge=2.5  hole=0.4  holeDia=0.8  ligament=1.7  pitch=3  rows=62 ...

As a library

var gen = require('./living-hinge-generator');
var r = gen.generate({ pattern: 'wave', width: 80, height: 300, bridge: 1.5 });
// r.svg     -> the SVG string
// r.stats   -> { slit, bridge, wavelength, ..., pattern, cuts }
// r.options -> every resolved option, after defaults and fitting

Note the library says height, the CLI says --length. Same thing.


3. Complete option reference

Panel

Option Default Notes
-p, --pattern <name> straight One of the eight in §5
-w, --width N 50 Span the slits cross
-l, --length N 200 Roll direction
--margin N 6 Solid end tab at each end. See §7
--units mm|in mm Output units only

Tuning (pattern-dependent)

Option Default Applies to
--pitch N 3 (3.5 torsional) straight, dogbone, wave, chevron, torsional
--bridge N 2 (3 dogbone) straight, dogbone, wave, chevron, torsional
--segments N scales with width the same five — whole number, min 2
--targetSlit N 12 the same five — what --segments aims for
--hole N 0.5 dogbone only — radius, not diameter
--cap N 1 torsional only — end-cap half-length
--wavelength N 13 wave, chevron
--amplitude N 1 wave, chevron — half-height
--step N 0.5 wave only — polyline resolution
--side N 5 honeycomb — target, gets fitted (§6)
--cell N 6.25 auxetic, crosshatch — target, gets fitted (§6)
--gap N 1.2 / 1.5 honeycomb, auxetic — uncut node size
--slit N 4.5 crosshatch only
--minSliver N 1 all — drop clipped cuts shorter than this

An option that does not apply to your pattern is reported on stderr and ignored, not silently dropped:

living-hinge: --hole does not apply to straight; ignoring it

Output & safety

Option Default Notes
-o, --out <file> — Complete path. Appends .svg if missing. Not combinable with --outdir/--all
--outdir <dir> . Created if absent
-a, --all — Every pattern at these dimensions
-f, --force — Overwrite. Default is to refuse
--dry-run — Stats only, no files
--maxCuts N 200000 Ceiling on emitted cuts
--maxPoints N 3200000 Ceiling on polyline coordinates

--maxCuts / --maxPoints exist so a mistyped feature size fails in a second instead of exhausting memory. You will only ever touch them for a genuinely enormous panel.

Input formats


flat-part-check does not fit a hinge, and that is not a fault

Running the flat gate in lasermade-tools over this folder gives 16 files, 115 checks, 21 failed. Every one of those failures is the tool's model, not the files:

What the tool IS worth running for here is fits the bed, and for that pass --min-edge 1.5, since its 3mm default is for a bullroarer's cord hole under string tension and nothing here is.

Every number here is a DRAWN number

Nothing in this generator compensates for the kerf, and neither does anything in the trumpet's bell or mouthpiece; only the two bore generators do. For a hinge that matters more than for most parts, because the dimension carrying the load is the bridge, and a bridge sits between two slits which each open by half a kerf as the beam goes down them.

On a laser measured at 0.13mm on 2026-09-09, the shipped sweep reads:

drawn 1.00  ->  0.87mm of ply actually left
drawn 1.25  ->  1.12mm
drawn 1.50  ->  1.37mm
drawn 1.75  ->  1.62mm
drawn 2.50  ->  2.37mm

This does not invalidate the sweep. Its whole method is to cut the coupons, flex them and keep the one that survives, and that answer is right whatever the label says. It matters when the number travels: ask for --bridge 1.5 in a later design expecting 1.5mm of ply and you get 1.37, and on a different laser you get something else again. Record the coupon that worked by its DRAWN value and the kerf you cut it at, and the pair transfers; the drawn value alone does not.

4. Suggested starting numbers

Unvalidated. Treat as a first guess to bracket with a coupon test.

Material --bridge --pitch --targetSlit Notes
3mm Baltic birch ply 1.5 – 2.0 3.0 12 The stock these are cut in. The shipped defaults sit at the stiff/strong end
3mm acrylic 2.0 – 2.5 3.0 – 3.5 12 Brittle. Go wider on the bridge, expect fewer bend cycles
1.5mm ply / card 1.0 – 1.5 2.0 – 2.5 8 – 10 Thinner stock tolerates a narrower ligament
6mm ply 2.5 – 3.5 4.0 – 5.0 15 – 20 Thick stock needs a long ligament to bend at all

The two knobs that matter most:

--targetSlit sets slit length indirectly: the generator picks segments = round(width / targetSlit), so slit length stays near your target as panels get wider. Prefer it over --segments, which hardcodes a count that stops making sense at a different width. If you pass both, --segments wins and you get a warning.


5. The eight patterns

Each is committed cut-ready in examples/ at 50 × 200mm — the size every figure below is quoted at — so the numbers in each section describe a file you can open. All eight span the full panel width, x = 0 to x = 50, which is the rule §1 says everything else serves.

previews/ holds a display rendering of each — gold is the panel that stays, cream the slits the laser removes — with the cut stroke thickened and painted onto the panel. Browse those; cut the ones in examples/. A real cut file is a 0.02mm hairline on no background at all, which most viewers show almost invisibly against a transparency checkerboard.

straight pattern: rows of parallel slits, offset row to row dogbone pattern: straight slits with a round relief hole at each end wave pattern: sinusoidal slits chevron pattern: zigzag slits
straight dogbone wave chevron
torsional pattern: I-cuts, straight slits with perpendicular end caps honeycomb pattern: a hexagonal lattice, bends on both axes auxetic pattern: rotating squares, bends on both axes crosshatch pattern: alternating short slits, bends on both axes
torsional honeycomb auxetic crosshatch

Click any one to download its cut file.

Stats below are real output at the default 50 × 200mm, margin 6.

straight — the default; start here

Plain staggered slits. Fewest cuts, fastest to cut, strongest for a given bridge.

====  ====  ====  ====        row A
  ====  ====  ====  ====      row B (offset half a period)
====  ====  ====  ====        row A

slit=11 bridge=2 pitch=3 rows=62 segments=4 → 279 cuts, 9.0 KB

dogbone — straight, with the stress risers removed

Adds a relief hole at each slit end. This is the fix for cracking at slit tips.

slit=10.25 bridge=3 hole=0.5 holeDia=1 ligament=2 pitch=3 rows=62 → 713 cuts, 28.5 KB

wave — sinusoidal slits

Longer ligament path for the same pitch, so it bends more easily than straight, and the curved path distributes stress instead of concentrating it.

slit=11 bridge=2 wavelength=13 amplitude=1 pitch=3 rows=62 → 279 cuts, 74.3 KB

chevron — zigzag slits

Wave's behaviour with straight-line segments. Cuts faster, file is 4× smaller (17 KB vs 74 KB), sharper look.

slit=11 bridge=2 wavelength=13 amplitude=1 pitch=3 rows=62 → 279 cuts, 17.2 KB

torsional — I-cuts (slit with perpendicular end caps)

Each slit gets a short perpendicular cap at each end, lengthening the torsion path. More compliant than straight at the same bridge.

slit=11 bridge=2 cap=1 pitch=3.5 rows=53 → 608 cuts, 20.3 KB

honeycomb — hex lattice (biaxial)

Compound curvature: bends on both axes. Hex vertices stay uncut and act as the hinge nodes.

side=4.8113 gap=1.2 slit=3.6113 → 481 cuts, 23.6 KB

auxetic — rotating squares (biaxial)

Squares joined only at their corners; those corners rotate. Negative Poisson's ratio — it expands laterally when stretched, so it forms domes rather than cylinders.

cell=6.25 gap=1.5 slit=4.75 → 518 cuts, 20.0 KB

crosshatch — alternating short slits (biaxial)

Checkerboard of horizontal and vertical slits. Fewest cuts of the three biaxial patterns and the simplest to reason about.

cell=6.25 slit=4.5 cells=8 x 30 edgeSlits=30 → 240 cuts, 11.4 KB

Picking one

Need Use
Simple single-axis bend straight
It cracked at the slit ends dogbone
More compliance, single axis chevron (or wave if you want the look)
Maximum compliance, single axis torsional
Cylinder in two directions honeycomb
Dome / saddle auxetic
Biaxial, minimum cut time crosshatch

6. --side and --cell are targets, not guarantees

The three lattice patterns resize their cell so the tiling divides the panel width exactly, putting a cut on both long edges. Without it you get an uncut rail up to ~1.2mm wide down one side, and the panel will not flex.

width 30mm   --side 5 → 4.9487   (1.0% off)     --cell 6.25 → 6.00 (4.0% off)
width 50mm   --side 5 → 4.8113   (3.8% off)     --cell 6.25 → 6.25 (exact)
width 75mm   --side 5 → 5.0943   (1.9% off)     --cell 6.25 → 6.25 (exact)
width 100mm  --side 5 → 5.0204   (0.4% off)     --cell 6.25 → 6.25 (exact)

Formulas: auxetic/crosshatch use cell = width / round(width / cell); honeycomb uses side = 2 × width / (√3 × m).

Deviation is typically under 4%. The delivered value appears in the stats line, the SVG <desc>, and the filename — so a file is always named for the geometry it contains. If you need an exact cell size, pick a width that is a whole multiple of it.


7. End tabs — and what happens when you minimise them

--margin is the solid band at each end of the length axis. It is what you glue, screw, or slot into the rest of the assembly. Cuts never enter it.

--margin Rows (50×200, pitch 3) First row at y=
0 66 2.5
1 66 2.5
3 64 5.5
6 (default) 62 8.5
12 58 14.5
20 53 22.0

You never get a zero end tab, even at --margin 0. Rows are centred in the band with a half-pitch offset, so the first row lands at least pitch/2 from the edge. At --margin 0 with pitch 3 the first row sits at y=2.5, leaving a 2.5mm solid strip. The effective tab is:

effective tab  =  margin  +  (leftover ÷ 2)  +  pitch/2      where leftover = span − rows × pitch

So the practical floor for the row-based patterns is about pitch/2 — roughly 1.5mm at default pitch. Two of the three biaxial patterns behave differently: their cuts are clipped straight at the band edge, so at --margin 0 honeycomb and auxetic really do cut to y=0 and the panel has no tab at all. Crosshatch is not one of them — its slits sit inside their cells rather than being clipped, so it floors at (cell − slit) ÷ 2: 0.875mm at the defaults, 1mm at --cell 10 --slit 8. That is under the pitch/2 floor of the row patterns and well under the 3mm glue surface recommended below, so treat it as no tab even though it is not zero.

Guidance


8. Sizing the hinge for a bend radius

A lattice hinge approximates an arc, so the hinge field length is arc length:

hinge length  =  radius × angle(radians)
rows          =  hinge length ÷ pitch
panel length  =  hinge length + 2 × margin
Target radius 90° needs 180° needs Rows @ pitch 3 (90°)
5mm 7.9mm 15.7mm 3
10mm 15.7mm 31.4mm 6
15mm 23.6mm 47.1mm 8
20mm 31.4mm 62.8mm 11
25mm 39.3mm 78.5mm 14
30mm 47.1mm 94.2mm 16
50mm 78.5mm 157.1mm 27
75mm 117.8mm 235.6mm 40

Read the other way — what a given panel can do at pitch 3, margin 6:

Panel length Hinge band Rows Gentlest 90° radius
40mm 28mm 9 ~17mm
60mm 48mm 16 ~31mm
80mm 68mm 22 ~42mm
100mm 88mm 29 ~55mm
200mm 188mm 62 ~118mm
300mm 288mm 96 ~183mm

A panel can bend tighter than the figure in the last column — you are then asking fewer rows to take more angle each, which is exactly what breaks ligaments. Treat that column as "the radius this panel reaches comfortably", and if you need tighter, add rows (longer panel or smaller pitch) rather than forcing it.

Fewer than ~6 rows in the bend behaves like a crease, not a curve.


9. Kerf

The generator emits zero-width centrelines and does no kerf compensation. Your beam removes material on both sides of every line, so the real ligament comes out narrower than the nominal --bridge.

actual ligament  ≈  bridge − kerf                       (straight, wave, chevron, torsional)
actual ligament  ≈  bridge − 2 × hole − kerf            (dogbone)

Typical CO₂ kerf in 3mm ply is 0.15–0.20mm. Dogbone at defaults:

Kerf hole 0.3 hole 0.4 hole 0.5 hole 0.6
0.10mm 2.30 2.10 1.90 1.70
0.15mm 2.25 2.05 1.85 1.65
0.20mm 2.20 2.00 1.80 1.60
0.25mm 2.15 1.95 1.75 1.55

(bridge 3, ligament in mm)

Add your kerf to --bridge if you want the nominal ligament you designed. Measure kerf once on your machine and stock: cut a 20mm square, measure it, the shortfall is one kerf.


10. Finding your real numbers

The defaults are geometry, not experience. One afternoon of coupons replaces all the guessing in §4.

cd living-hinge          # wherever you cloned or unzipped it
mkdir -p coupons
for b in 1.0 1.25 1.5 1.75 2.0 2.5; do
  ./living-hinge.js -w 40 -l 120 --bridge $b --outdir ./coupons
done

Each lands in its own file, since differing tuning goes into the filename:

living-hinge-straight-40x120mm-bridge1.svg
living-hinge-straight-40x120mm-bridge1.25.svg
living-hinge-straight-40x120mm-bridge1.5.svg
living-hinge-straight-40x120mm-bridge1.75.svg
living-hinge-straight-40x120mm.svg            <- bridge 2.0, the default, keeps the plain name
living-hinge-straight-40x120mm-bridge2.5.svg

These six are committed, in coupons/, along with a single 290 × 132mm sheet holding all of them — coupons/coupon-sheet-bridge-sweep-290x132mm.svg — and coupons/README.md, which records the settings held constant across the sweep. You can cut the sheet without running anything.

Cut all six on one sheet, in the grain orientation you will actually use. Then:

  1. Bend each to 90°. Note which snap immediately.
  2. Cycle the survivors 20–30 times. Fatigue is what kills living hinges in service, not the first bend.
  3. Bend one to destruction to find the margin you actually have.
  4. Record the narrowest bridge that survives cycling, then add 0.25mm as your working value.

Repeat per material and per thickness. Grain direction matters in ply — slits running across the grain behave differently from slits running along it, so test the orientation you will build in.

When you have real numbers, say so and they will be written into DEFAULTS in the generator and into the project memory, so future sessions start from measurements instead of estimates.


11. Output format

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
     width="50mm" height="200mm" viewBox="0 0 50 200">
  <title>Living hinge - straight - 50mm x 200mm</title>
  <desc>1 user unit = 1mm. Panel 50mm x 200mm, solid end tabs 6mm. Pattern: straight; ...</desc>
  <g fill="none" stroke="#000000" stroke-width="0.02" stroke-linecap="butt">
    <g id="outline"><rect x="0" y="0" width="50" height="200"/></g>
    <g id="hinge-slits"> ... </g>
  </g>
</svg>

Filenames

living-hinge-<pattern>-<width>x<length><units>[-<tuning>].svg

Default runs stay short (living-hinge-straight-50x200mm.svg). Any option differing from that pattern's default is appended (-bridge3, -margin20, -pitch2.5-bridge4), so iterating never overwrites the previous variant. Fitted values are recorded as delivered (-side5.7735, not -side6).

Existing files are never overwritten without --force.


12. When it refuses

The generator declines anything that would destroy the panel rather than cutting it. Every message names both the offending option and the way out.

Message Cause Fix
relief holes of radius 1.5mm meet across a 3mm bridge (ligament 0mm) Dogbone holes overlap; bridge cut through Lower --hole or raise --bridge
caps of 3mm meet across a 3.5mm pitch Torsional caps merge across rows Lower --cap or raise --pitch
gap 6mm is not shorter than the 4.8113mm hex side No cut left after node shortening Lower --gap or raise --side
slit 14mm spans 12.5mm of alternating cells Crosshatch slits merge into a continuous cut Lower --slit or raise --cell
bridge 20mm is too wide for 4 segments across 50mm No room for slits Lower --bridge or widen the panel
pitch 30mm leaves no room for a row in the 8mm between the end tabs Panel too short for one row Lower --pitch, lengthen, or lower --margin
every cut came out shorter than --minSliver (1mm) and was dropped Feature smaller than the sliver threshold Raise the cut length or lower --minSliver
--segments must be at least 2 One segment spans the full width Use ≥ 2
--pitch must be greater than 0mm Zero/negative length Positive values only
cut budget exceeded (--maxCuts 200000) Feature size tiny for the panel Coarsen, or raise --maxCuts
polyline point budget exceeded (--maxPoints 3200000) --step far too fine Raise --step, or raise --maxPoints

Set LIVING_HINGE_DEBUG=1 for a stack trace on unexpected errors.


13. Scripting

./living-hinge.js --all --width 5 --outdir ./tiny || echo "some patterns failed"

14. Gotchas


15. Known limitations