Living Hinge Generator — Operating Guide

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

FileRole
living-hinge-generator.jsLibrary. require() it, call generate({...}){svg, stats, options}
living-hinge.jsCLI 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.5  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

OptionDefaultNotes
-p, --pattern <name>straightOne of the eight in §5
-w, --width N50Span the slits cross
-l, --length N200Roll direction
--margin N6Solid end tab at each end. See §7
`--units mm\in`mmOutput units only

Tuning (pattern-dependent)

OptionDefaultApplies to
--pitch N3 (3.5 torsional)straight, dogbone, wave, chevron, torsional
--bridge N2 (3 dogbone)straight, dogbone, wave, chevron, torsional
--segments Nscales with widththe same five — whole number, min 2
--targetSlit N12the same five — what --segments aims for
--hole N0.5dogbone only — radius, not diameter
--cap N1torsional only — end-cap half-length
--wavelength N13wave, chevron
--amplitude N1wave, chevron — half-height
--step N0.5wave only — polyline resolution
--side N5honeycomb — target, gets fitted (§6)
--cell N6.25auxetic, crosshatch — target, gets fitted (§6)
--gap N1.2 / 1.5honeycomb, auxetic — uncut node size
--slit N4.5crosshatch only
--minSliver N1all — 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

OptionDefaultNotes
-o, --out <file>Complete path. Appends .svg if missing. Not combinable with --outdir/--all
--outdir <dir>.Created if absent
-a, --allEvery pattern at these dimensions
-f, --forceOverwrite. Default is to refuse
--dry-runStats only, no files
--maxCuts N200000Ceiling on emitted cuts
--maxPoints N3200000Ceiling 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--targetSlitNotes
3mm Baltic birch ply1.5 – 2.03.012The stock these are cut in. The shipped defaults sit at the stiff/strong end
3mm acrylic2.0 – 2.53.0 – 3.512Brittle. Go wider on the bridge, expect fewer bend cycles
1.5mm ply / card1.0 – 1.52.0 – 2.58 – 10Thinner stock tolerates a narrower ligament
6mm ply2.5 – 3.54.0 – 5.015 – 20Thick 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=4279 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=62713 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=62279 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=62279 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=53608 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.6113481 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.75518 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=30240 cuts, 11.4 KB

Picking one

NeedUse
Simple single-axis bendstraight
It cracked at the slit endsdogbone
More compliance, single axischevron (or wave if you want the look)
Maximum compliance, single axistorsional
Cylinder in two directionshoneycomb
Dome / saddleauxetic
Biaxial, minimum cut timecrosshatch

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.

--marginRows (50×200, pitch 3)First row at y=
0662.5
1662.5
3645.5
6 (default)628.5
125814.5
205322.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 radius90° needs180° needsRows @ pitch 3 (90°)
5mm7.9mm15.7mm3
10mm15.7mm31.4mm6
15mm23.6mm47.1mm8
20mm31.4mm62.8mm11
25mm39.3mm78.5mm14
30mm47.1mm94.2mm16
50mm78.5mm157.1mm27
75mm117.8mm235.6mm40

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

Panel lengthHinge bandRowsGentlest 90° radius
40mm28mm9~17mm
60mm48mm16~31mm
80mm68mm22~42mm
100mm88mm29~55mm
200mm188mm62~118mm
300mm288mm96~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:

Kerfhole 0.3hole 0.4hole 0.5hole 0.6
0.10mm2.302.101.901.70
0.15mm2.252.051.851.65
0.20mm2.202.001.801.60
0.25mm2.151.951.751.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.

MessageCauseFix
relief holes of radius 1.5mm meet across a 3mm bridge (ligament 0mm)Dogbone holes overlap; bridge cut throughLower --hole or raise --bridge
caps of 3mm meet across a 3.5mm pitchTorsional caps merge across rowsLower --cap or raise --pitch
gap 6mm is not shorter than the 4.8113mm hex sideNo cut left after node shorteningLower --gap or raise --side
slit 14mm spans 12.5mm of alternating cellsCrosshatch slits merge into a continuous cutLower --slit or raise --cell
bridge 20mm is too wide for 4 segments across 50mmNo room for slitsLower --bridge or widen the panel
pitch 30mm leaves no room for a row in the 8mm between the end tabsPanel too short for one rowLower --pitch, lengthen, or lower --margin
every cut came out shorter than --minSliver (1mm) and was droppedFeature smaller than the sliver thresholdRaise the cut length or lower --minSliver
--segments must be at least 2One segment spans the full widthUse ≥ 2
--pitch must be greater than 0mmZero/negative lengthPositive values only
cut budget exceeded (--maxCuts 200000)Feature size tiny for the panelCoarsen, or raise --maxCuts
polyline point budget exceeded (--maxPoints 3200000)--step far too fineRaise --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