Kerf language reference

Every built-in and library module and function, with its arguments and defaults. Generated from the source (tools/gen_reference.mjs), so it matches this version of Kerf.

Language

UnitsLengths are in millimetres and angles in degrees. x runs right and y up on the sheet; z is up in 3D.
ValuesNumbers, strings ("ply3"), true/false, undef, lists [1, 2, 3] and ranges [start : end] or [start : step : end]. Lists index from 0 (p[0]) and .x .y .z read items 0–2.
Variablesw = 40; Like OpenSCAD, the last assignment in a scope wins, everywhere in that scope.
Operators+ - * / % ^, comparisons == != < <= > >=, && || ! and cond ? a : b. + - * work on lists (vector maths; * of two lists is a dot or matrix product).
Functionsfunction area(a, b = 2) = a * b; Function literals work too: f = function(x) x * 2;
Modulesmodule plate(w, h = 20) { square([w, h]); }, called as plate(30); or plate(w = 30, h = 10);. children() inside a module is whatever it was called with.
Controlfor (i = [0 : 3]) …, for (x = [..], y = [..]) (nested), if (c) … else …, let (a = 1) …, and list comprehensions [for (a = [0 : 30 : 330]) [cos(a), sin(a)] * 10] with if and each.
Filesinclude <file.kerf> pastes a file in; use <file.kerf> imports only its modules and functions. Paths are relative to the current file, then the built-in libraries.
ModifiersBefore any module call: % ghost (shown, not cut; reference solids count in the fit check), # highlight, ! show only this, * disable.
Special variables$fn, $fa, $fs control circle smoothness (see 2D shapes). PI is π.

Built-in modules and functions

Always available, no use needed.

Materials and parts

Everything that gets cut belongs to a part. A part is one flat piece of one material, drawn in 2D and placed in 3D by the transforms around it.

module material(name = "default", thickness = 3, color = "wood", opacity = 1, kerf = 0.1, sheet = [600, 400], margin = 5, gap = 3)

Defines (or changes) a material. Later calls with the same name update only the settings they give. Without any material() call, parts use a 3 mm default.

argumentdefault
name"default"name you refer to it by, e.g. "ply3"
thickness3sheet thickness in mm
color"wood"colour in the 3D view: a name ("wood", "clear", "red"…), "#rrggbb" or [r, g, b] 0–1
opacity1opacity in the 3D view (0–1), e.g. 0.4 for acrylic
kerf0.1width of the laser cut in mm; outlines are offset by half of it on export (see tutorial lesson 9)
sheet[600, 400]sheet size [w, h] in mm, for sheet layout
margin5empty border around each sheet in mm
gap3space between parts on a sheet in mm
material("ply3", thickness = 3, color = "wood", kerf = 0.12, sheet = [300, 300]);

module part(name = "part", material = undef, qty = 1, thickness = undef, center = false, color = undef)

Makes its 2D children into one cut part. Transforms outside the part place it in 3D; the part lies on z = 0 to thickness before that. Holes are anything subtracted with difference().

argumentdefault
name"part"name in the parts list and on the sheets
materialundefmaterial name; default: the first material defined
qty1how many to cut (shown once in 3D, laid out qty times)
thicknessundefinstead of material: a plain material of this thickness
centerfalsetrue centres the thickness on z = 0
colorundefcolour in the 3D view, overriding the material's
translate([0, 0, 20]) part("lid", "ply3", qty = 2) difference() {
  square([80, 60]);
  translate([40, 30]) circle(d = 10);
}

function thickness(material)

A material's thickness in mm.

argumentdefault
materialrequiredmaterial name
t = thickness("ply3");

function kerf(material)

A material's kerf (laser cut width) in mm.

argumentdefault
materialrequiredmaterial name

function sheet_size(material)

The usable area of one sheet: [w, h] minus the margins.

argumentdefault
materialrequiredmaterial name

Laser operations

Inside a part, plain shapes are cut. These wrap shapes to engrave or score them instead.

module engrave()

Fill-engraves the children's areas (exported as black fill). Lines inside become scores.

part("tag", "ply3") {
  square([40, 15]);
  engrave() translate([20, 7.5]) text("HELLO", size = 8, halign = "center", valign = "center");
}

module score()

Line-engraves the outlines of the children without cutting (exported as blue lines).

module cut()

Cuts the children's outlines as lines without making them holes, e.g. a cut line through a living hinge.

module line(points, closed = false)

An open line (not an area). Cut inside a part, or scored inside engrave()/score().

argumentdefault
pointsrequiredlist of [x, y] points
closedfalsetrue joins the last point back to the first
part("card", "ply3") {
  square([40, 20]);
  score() line([[5, 10], [20, 15], [35, 10]]);   // a scored fold line
}

module image(file, width = undef, height = undef, dpi = 300, center = false)

A bitmap to engrave (embedded in the exported SVG). Give width or height and the other follows the image's shape.

argumentdefault
filerequiredpath relative to the .kerf file
widthundefwidth in mm
heightundefheight in mm
dpi300size from the image's pixels when neither width nor height is given
centerfalsecentre on the origin
engrave() image("photo.png", width = 50);

2D shapes

All sizes in mm. Curves are made of short straight segments; $fn sets the number of segments, or $fa (max degrees per segment, default 1) and $fs (max segment length, default 0.1 mm).

module square(size = 1, center = false)

A rectangle with its corner at the origin.

argumentdefault
size1a number (square) or [w, h]
centerfalsecentre on the origin

module circle(r = 1, d = undef)

A circle centred on the origin.

argumentdefault
r1radius
dundefdiameter, instead of r
circle(d = 3.2);

module polygon(points, paths = undef)

A polygon from points. With paths, the first path is the outline and the rest are holes.

argumentdefault
pointsrequiredlist of [x, y] points
pathsundeflists of point indexes, one per outline

module text(text, size = 10, font = "Liberation Sans", halign = "left", valign = "baseline", spacing = 1)

Text as outlines. Bundled fonts and .ttf/.otf files in the project both work.

argumentdefault
textrequiredthe text
size10roughly the height of capital letters in mm
font"Liberation Sans"font name, optionally with a style: "Liberation Sans:style=Bold", or a font file
halign"left""left", "center" or "right"
valign"baseline""baseline", "top", "center" or "bottom"
spacing1letter spacing factor

module import(file, center = false)

Shapes from an SVG file. Closed paths fill even-odd (like most drawing programs); open paths become lines.

argumentdefault
filerequiredpath to a .svg relative to the .kerf file
centerfalsecentre on the origin

function round_corners(points, r = 0)

A closed path (list of points) with its corners rounded. A radius too big for its edges is reduced, with a warning.

argumentdefault
pointsrequiredcorner points of a closed outline
r0one radius for every corner, or a list with one per point (0 = sharp)
polygon(round_corners([[0, 0], [60, 0], [60, 40], [0, 40]], [0, 10, 10, 0]));

Transforms

Each applies to its children. Angles are in degrees.

module translate(v = [0, 0, 0])

Moves the children.

argumentdefault
v[0, 0, 0][x, y] or [x, y, z]

module rotate(a = 0, v = undef)

Rotates the children. A single number rotates about z.

argumentdefault
a0angle, or [x, y, z] angles applied in that order
vundefaxis to rotate a degrees around

module scale(v = 1)

Scales the children.

argumentdefault
v1factor, or [x, y] / [x, y, z] factors

module mirror(v = [1, 0, 0])

Mirrors the children across the plane through the origin with normal v.

argumentdefault
v[1, 0, 0]normal of the mirror; [1, 0] flips left–right

module multmatrix(m)

Applies a 4 × 4 (or 3 × 4) transformation matrix.

argumentdefault
mrequiredthe matrix, as a list of rows

module resize(newsize = [0, 0], auto = false)

Scales 2D children to an exact size.

argumentdefault
newsize[0, 0][w, h]; 0 leaves that direction alone
autofalsetrue scales a 0 direction to keep proportions

module offset(r = undef, delta = undef, chamfer = false)

Grows (positive) or shrinks (negative) 2D shapes.

argumentdefault
rundefround offset: corners become arcs
deltaundefsharp offset: corners stay sharp
chamferfalsewith delta: cut the corners off
offset(r = 2) square([20, 10]);

module color(c, alpha = 1)

Colour of the children in the 3D view (parts and reference solids). Doesn't affect the export.

argumentdefault
crequireda name, "#rrggbb" or [r, g, b] 0–1
alpha1opacity

Combining shapes

module union()

Merges the children into one shape. Shapes inside a part are merged anyway.

module difference()

The first child minus all the others: how holes are made.

module intersection()

Only the area all the children share.

module hull()

The convex hull of the children: like a rubber band around them.

hull() { circle(5); translate([30, 0]) circle(8); }

module minkowski()

Minkowski sum of the children: traces the second shape around the first (rounds a square with a circle).

module group()

Groups children without changing them.

module render()

Accepted for OpenSCAD compatibility; does nothing.

module children(index = undef)

Inside a module: the children it was called with.

argumentdefault
indexundefonly this child (from 0)

Reference solids (preview only)

3D shapes for test-fitting real objects. They show in the 3D view, count in the fit check and are never cut. Use them with % so they show as ghosts, e.g. %cylinder(h = 60, d = 30);.

module cube(size = 1, center = false)

A box with its corner at the origin.

argumentdefault
size1a number or [x, y, z]
centerfalsecentre on the origin

module cylinder(h = 1, r = 1, d = undef, r1 = r, r2 = r1, d1 = undef, d2 = undef, center = false)

A cylinder (or cone) standing on z = 0.

argumentdefault
h1height
r1radius
dundefdiameter, instead of r
r1rbottom radius
r2r1top radius
d1undefbottom diameter
d2undeftop diameter
centerfalsecentre the height on z = 0

module sphere(r = 1, d = undef)

A sphere centred on the origin.

argumentdefault
r1radius
dundefdiameter, instead of r

Curved walls (flex)

The building blocks behind flex_box in flex.kerf. path is the outside of the walls seen from above, as a closed list of points; rounded stretches bend with a living hinge and sharp 90° corners split the wall into panels with finger-jointed corners. Everything is measured along the middle of the sheet. Every piece of one box must get the same path, t, fw, sag, margin and max_len, or the notches won't line up (the fit check shows it). Unlike flex_box, these don't read the sheet size: give max_len = max(sheet_size("ply3")) to split long walls.

module flex_plate(path, t, lip = 0, fw = 12, sag = 0.1, margin = 3, max_len = undef)

Bottom or top plate: the outline with notches for the wall's fingers (lip = 0, flush) or slots they poke through (lip > 0).

argumentdefault
pathrequiredwall outline
trequiredsheet thickness, or a material name
lip0how far the plate overhangs the walls, in mm (0 = flush notches)
fw12target finger width on straight stretches
sag0.1how far a straight finger may stand off a curve (mm): sets finger width on curves, √(8·r·sag)
margin3how far the hinge cuts run past each bend
max_lenundeflongest flat panel; longer walls split at butt seams

module flex_panel(path, height, t, panel = 0, top = true, fw = 12, sag = 0.1, margin = 3, max_len = undef, pitch = 1.5, seg = 18, bridge = 3)

One flat wall panel: fingers along the bottom (and top) edges, hinge cuts where it bends, finger corners at sharp corners.

argumentdefault
pathrequiredwall outline
heightrequiredoutside height, plates included
trequiredsheet thickness, or a material name
panel0which panel (from 0; see flex_count)
toptruefalse: no fingers on the top edge (open box)
fw12as flex_plate
sag0.1as flex_plate
margin3as flex_plate
max_lenundefas flex_plate
pitch1.5spacing between hinge cut lines
seg18length of each hinge cut
bridge3uncut gap between hinge cuts

module flex_bend(path, t, panel = 0, fw = 12, sag = 0.1, margin = 3, max_len = undef)

Shows a flat panel part bent into place in 3D, and fit-checks it bent. It is still laid out and cut flat. Wrap it around the part.

argumentdefault
pathrequiredwall outline
trequiredsheet thickness, or a material name
panel0which panel
fw12as flex_plate
sag0.1as flex_plate
margin3as flex_plate
max_lenundefas flex_plate
for (i = [0 : flex_count(shape, t) - 1])
  flex_bend(shape, t, i) part(str("wall", i), "ply3") flex_panel(shape, 60, t, i);

function flex_count(path, t, fw = 12, sag = 0.1, margin = 3, max_len = undef)

How many wall panels the outline needs.

argumentdefault
pathrequiredwall outline
trequiredsheet thickness
fw12as flex_plate
sag0.1as flex_plate
margin3as flex_plate
max_lenundefas flex_plate

function flex_lengths(path, t, fw = 12, sag = 0.1, margin = 3, max_len = undef)

The flat length of each wall panel, for planning sheets.

argumentdefault
pathrequiredwall outline
trequiredsheet thickness
fw12as flex_plate
sag0.1as flex_plate
margin3as flex_plate
max_lenundefas flex_plate

Messages

module echo()

Prints values to the console: echo(w = w) prints w = 40.

module assert(condition, message = undef)

Stops with an error if the condition is false.

argumentdefault
conditionrequired
messageundefshown with the error
assert(w > 2 * t, "too narrow");

Math and lists

Trigonometry works in degrees. PI is π.

function abs(x)

Absolute value.

function sign(x)

-1, 0 or 1.

function floor(x)

Round down.

function ceil(x)

Round up.

function round(x)

Round to the nearest whole number (halves away from 0).

function sqrt(x)

Square root.

function exp(x)

e to the power x.

function ln(x)

Natural logarithm.

function log(x)

Base-10 logarithm.

function pow(base, exponent)

base to the power exponent (also base ^ exponent).

function sin(a)

Sine of an angle in degrees. Also cos, tan.

argumentdefault
arequireddegrees

function cos(a)

Cosine of an angle in degrees.

argumentdefault
arequireddegrees

function tan(a)

Tangent of an angle in degrees.

argumentdefault
arequireddegrees

function asin(x)

Inverse sine, in degrees. Also acos, atan.

function acos(x)

Inverse cosine, in degrees.

function atan(x)

Inverse tangent, in degrees.

function atan2(y, x)

Angle of the point [x, y] in degrees (note the order: y first).

function min(values…)

Smallest of the arguments, or of one list.

argumentdefault
values…requirednumbers, or one list

function max(values…)

Largest of the arguments, or of one list.

argumentdefault
values…requirednumbers, or one list

function norm(v)

Length of a vector.

function cross(a, b)

Cross product (a number for 2D vectors).

function len(v)

Number of items in a list, or characters in a string.

function concat(lists…)

Joins lists into one list.

function lookup(key, table)

Interpolates in a table of [key, value] rows sorted by key.

echo(lookup(2.5, [[0, 0], [10, 100]]));   // 25

function rands(lo, hi, n, seed = undef)

A list of n random numbers between lo and hi. With a seed, the same every time.

argumentdefault
lorequired
hirequired
nrequired
seedundef

function str(values…)

Joins values into a string.

part(str("wall", i), "ply3") …

function chr(codes…)

Characters from character codes.

function ord(s)

Character code of the first character.

function is_undef(v)

True if the value is undef. Also is_num, is_bool, is_string, is_list, is_function.

function is_num(v)

True if the value is a number.

function is_bool(v)

True if the value is a boolean.

function is_string(v)

True if the value is a string.

function is_list(v)

True if the value is a list.

function is_function(v)

True if the value is a function.

function version()

Kerf's version as [year, month, n].

Libraries

Written in Kerf (in lib/). use <name.kerf> imports a library's modules and functions; a file with the same name in your project takes precedence.

Standard library

Kerf standard library — loaded automatically before every file. Everything here is plain Kerf; copy and tweak freely.

shapes

module rounded_square(size, r = 2, center = false)

A rectangle with rounded corners.

argumentdefault
sizerequireda number (square) or [w, h]
r2corner radius (reduced if it's too big for the size)
centerfalsetrue centres it on the origin; false puts its corner there

module ngon(n, r)

A regular polygon centred on the origin, with a corner on +x.

argumentdefault
nrequirednumber of sides
rrequiredcircumradius (centre to corner)

module slot(len, w)

A slot with round ends, centred on the origin, running along x.

argumentdefault
lenrequiredoverall length, round ends included
wrequiredwidth (the ends' diameter)
joints

function finger_count(len, w = 10)

Odd number of fingers that fit len with fingers about w wide.

argumentdefault
lenrequirededge length
w10target finger width

module finger_tabs(len, t, n = undef, odd = true, w = 10)

Tabs along the x axis from 0..len, t deep (y = 0..t). The edge is split into n segments and every other one is kept. Two mating edges use the same len and n and opposite odd.

argumentdefault
lenrequirededge length
trequiredtab depth (the thickness of the mating sheet)
nundefnumber of segments; default finger_count(len, w)
oddtruetrue keeps segments 1, 3, 5…; false keeps 0, 2, 4…
w10target finger width, when n isn't given

module finger_slots(len, t, n = undef, odd = true, w = 10)

Slots through a panel for tabs of another panel crossing it (a "tab and slot" joint away from the edge). Slots run along x from 0..len, t wide, centred on y = 0. Use odd/n matching the tabs.

argumentdefault
lenrequiredas finger_tabs
trequiredslot width (the thickness of the sheet with the tabs)
nundefas finger_tabs
oddtrueas finger_tabs
w10as finger_tabs

module tslot(d = 3, len = 12, nut_w = 5.5, nut_t = 2.4, nut_at = 6)

T-slot for a captive nut + bolt, cut into an edge. The edge lies on y = 0 and the slot goes inward along +y. Subtract it from the panel.

argumentdefault
d3bolt diameter (the slot's width)
len12how far the slot goes into the panel (the bolt's length past the other panel)
nut_w5.5nut width across the flats
nut_t2.4nut thickness
nut_at6distance from the edge to the nut

module tslot_hole(d = 3)

Matching bolt hole for a T-slot in the panel it screws through.

argumentdefault
d3bolt diameter
laser tricks

module living_hinge(w, h, pitch = 1.5, seg = 18, bridge = 3)

Straight-line living hinge: cut lines parallel to y across a w × h area so the sheet bends around an axis parallel to y. The flex.kerf boxes lay these out for you.

argumentdefault
wrequiredsize of the bendable area (bending happens across w)
hrequiredsize of the bendable area (bending happens across w)
pitch1.5spacing between cut lines; smaller bends more easily
seg18length of each cut
bridge3material left between cuts; smaller bends more easily

module hole_grid(cols, rows, pitch, d)

A grid of round holes (subtract it), handy for vents and speaker grilles. The first hole is centred on the origin.

argumentdefault
colsrequirednumber of holes along x and y
rowsrequirednumber of holes along x and y
pitchrequiredcentre-to-centre spacing
drequiredhole diameter
3D placement

module face_y(t)

Stands a flat part up so its local +y becomes world +z, facing -y. Local x stays world x; its thickness occupies y = 0..t.

argumentdefault
trequiredthe part's thickness

module face_x(t)

Stands a flat part up so its local +y becomes world +z, facing +x. Local x runs along world +y; its thickness occupies x = 0..t.

argumentdefault
trequiredthe part's thickness

Boxes use <boxes.kerf>

Parametric finger-jointed boxes.

finger_box([120, 80, 60], "ply3");                  // open box, all parts placed in 3D
finger_box([120, 80, 60], "ply3", top = "lid");     // with a lift-off lid
finger_box([100, 60, 40], "ply3", inside = true);   // size is the inside

Sizes are outside dimensions [x, y, z] unless inside = true. The box sits with its outside corner at the origin. Engraving on the walls faces out.

To cut holes or engrave a panel, build the parts yourself from the pieces:

size = [120, 80, 60]; t = thickness("ply3");
for (w = ["bottom", "back", "left", "right"]) box_place(w, size, t) part(w, "ply3") box_panel(w, size, t);
box_place("front", size, t) part("front", "ply3") difference() {
  box_panel("front", size, t);
  translate([60, 30]) circle(d = 20);
}

Arguments used throughout:

sizethe box's outside size [x, y, z]
tsheet thickness (also the finger depth)
fwtarget finger width
top"open", "closed", or "lid" (a lift-off lid: a plate plus a lip that drops inside the walls; glue the two together)
whichwhich panel: "bottom", "top", "front", "back", "left" or "right"

module fj_panel(w, h, t, edges = [1, 2, 0, 2], fw = 12)

One flat panel w × h with finger-jointed edges. Two mating edges: one 1, the other 2.

argumentdefault
wrequiredpanel size
hrequiredpanel size
trequiredsheet thickness (also the finger depth)
edges[1, 2, 0, 2][bottom, right, top, left], each 0 plain, 1 fingers that fit between the other panel's, 2 fingers that own the corners
fw12target finger width

module box_panel(which, size, t, top = "open", fw = 12)

The 2D outline of one panel of a standard box (top = "lid" gives the walls of an open box). Front/back own the vertical corners; bottom/top own their corners.

argumentdefault
whichrequiredwhich panel: "bottom", "top", "front", "back", "left" or "right"
sizerequiredthe box's outside size [x, y, z]
trequiredsheet thickness (also the finger depth)
top"open""open", "closed", or "lid" (a lift-off lid: a plate plus a lip that drops inside the walls; glue the two together)
fw12target finger width

module box_place(which, size, t)

Put a part where a panel goes in a box of size with its outside corner at the origin. Walls face outward: the side you engrave ends up outside. Front/back panels are size.x wide; left/right run along y (local x = y for "right", reversed for "left", so text reads correctly from outside).

argumentdefault
whichrequiredwhich panel: "bottom", "top", "front", "back", "left" or "right"
sizerequiredthe box's outside size [x, y, z]
trequiredsheet thickness (also the finger depth)

function box_outside(size, t, top = "open")

Outside size of a box from its inside size.

argumentdefault
sizerequiredthe inside size [x, y, z]
trequiredsheet thickness (also the finger depth)
top"open""open", "closed", or "lid" (a lift-off lid: a plate plus a lip that drops inside the walls; glue the two together)

module finger_box(size, material, top = "open", inside = false, fw = 12, gap = 0.3, label = "", font = "Liberation Sans:style=Bold", label_size = 10, name = "box")

A whole box as parts named <name>_bottom, <name>_front, ..., placed in 3D with the outside corner at the origin.

argumentdefault
sizerequiredoutside size [x, y, z] (inside size with inside = true)
materialrequiredmaterial name
top"open""open", "closed", or "lid" (a lift-off lid: a plate plus a lip that drops inside the walls; glue the two together)
insidefalsetrue = size is the inside
fw12target finger width
gap0.3clearance around the lid lip
label""text engraved on the front (or on the top / lid)
font"Liberation Sans:style=Bold"the label's font
label_size10the label's text size
name"box"start of every part's name

Hinges use <hinges.kerf>

Hinges.

hinged_box([120, 80, 50], "ply3", lid_h = 20, pin_d = 3);

living_hinge() (in the standard library) makes a sheet bendable; bend_length() tells you how long the flexible area needs to be.

function bend_length(angle, r, t)

Length of living hinge needed to bend a sheet of thickness t through angle degrees around an inside radius r.

argumentdefault
anglerequiredbend angle in degrees
rrequiredinside radius of the bend
trequiredsheet thickness

module pivot_ear(at, r, d, a, b)

A round ear with a pin hole at at, joined to the straight edge from a to b (two points on the panel's outline). Union it with the panel.

argumentdefault
atrequiredcentre of the ear and its hole
rrequiredear radius
drequiredpin hole diameter
arequiredends of the panel edge the ear grows from
brequiredends of the panel edge the ear grows from

module hinged_box(size, material, lid_h = 20, pin_d = 3.2, knuckle = undef, gap = 0.5, fw = 12, open = 0, label = "", font = "Liberation Sans:style=Bold", label_size = 12, name = "box")

Box with a lid that swings open on two pins (dowel, filament, M3 bolt...). The lid is a shallow upside-down box, slightly wider than the body, whose sides hang over the body's sides at the back. Both have ears behind the box with a pin hole; the pivot is knuckle mm behind the back and below the rim, which lets the lid open past 90° without hitting anything. Parts: <name>_bottom/_front/_back/_left/_right, <name>_lid_top/_lid_front/...

argumentdefault
sizerequiredthe body's outside size [x, y, z]
materialrequiredmaterial name
lid_h20lid height (outside)
pin_d3.2pin hole diameter (make it a touch bigger than the pin)
knuckleundefhow far the pivot is behind the back and below the rim; default pin_d + 3
gap0.5clearance between the lid's sides and the body's
fw12target finger width
open0lid angle in degrees, for test-fitting in the 3D view
label""text engraved on the lid
font"Liberation Sans:style=Bold"the label's font
label_size12the label's text size
name"box"start of every part's name

Gears use <gears.kerf>

Involute spur gears.

Gears mesh when they share mod (tooth size: pitch diameter / teeth) and pressure_angle. Every gear has a tooth centred on +x.

gear(20, mod = 2, bore = 5);                       // 20 teeth, 40 mm pitch circle
gear_mesh(20, 12, mod = 2) gear(12, mod = 2);      // a 12-tooth gear meshing with it
d = gear_distance(20, 12, mod = 2);                // centre distance = 32 mm

Below about 13 teeth real gears are undercut; these aren't, so very small pinions can bind. Laser-cut gears like a little extra centre distance (0.1–0.2 mm) on top of gear_distance() — try it on a test pair first.

Arguments used throughout:

teethnumber of teeth
modmodule, the tooth size: pitch diameter ÷ teeth. Meshing gears share it
pressure_angletooth flank angle in degrees. Meshing gears share it
backlashtooth thinning in mm, the play between meshing teeth
t1tooth counts: t1 of gear 1 (at the origin), t2 of gear 2
t2tooth counts: t1 of gear 1 (at the origin), t2 of gear 2
a1how far gear 1 is turned, in degrees
dirdirection from gear 1 to gear 2, in degrees
extraadded centre distance in mm
sizes

function gear_pitch_r(teeth, mod = 2)

where the gears roll on each other

argumentdefault
teethrequirednumber of teeth
mod2module, the tooth size: pitch diameter ÷ teeth. Meshing gears share it

function gear_outer_r(teeth, mod = 2)

tooth tips

argumentdefault
teethrequirednumber of teeth
mod2module, the tooth size: pitch diameter ÷ teeth. Meshing gears share it

function gear_root_r(teeth, mod = 2)

bottom of the gaps

argumentdefault
teethrequirednumber of teeth
mod2module, the tooth size: pitch diameter ÷ teeth. Meshing gears share it

function gear_distance(t1, t2, mod = 2)

centre distance for meshing

argumentdefault
t1requiredtooth counts: t1 of gear 1 (at the origin), t2 of gear 2
t2requiredtooth counts: t1 of gear 1 (at the origin), t2 of gear 2
mod2module, the tooth size: pitch diameter ÷ teeth. Meshing gears share it

function gear_ratio(t1, t2)

turns of gear 1 per turn of gear 2

argumentdefault
t1requiredtooth counts: t1 of gear 1 (at the origin), t2 of gear 2
t2requiredtooth counts: t1 of gear 1 (at the origin), t2 of gear 2

function gear_mesh_angle(t1, t2, a1 = 0, dir = 0)

Rotation for gear 2 so its teeth fall between gear 1's. Gear 1 sits at the origin turned by a1; gear 2 sits in direction dir (degrees) from it. Turning gear 1 by a1 turns gear 2 the other way by a1 * t1 / t2.

argumentdefault
t1requiredtooth counts: t1 of gear 1 (at the origin), t2 of gear 2
t2requiredtooth counts: t1 of gear 1 (at the origin), t2 of gear 2
a10how far gear 1 is turned, in degrees
dir0direction from gear 1 to gear 2, in degrees

module gear_mesh(t1, t2, mod = 2, a1 = 0, dir = 0, extra = 0)

Place children (gear 2, t2 teeth) meshing with a t1-tooth gear at the origin. Works for flat shapes and for parts. extra = added centre distance.

argumentdefault
t1requiredtooth counts: t1 of gear 1 (at the origin), t2 of gear 2
t2requiredtooth counts: t1 of gear 1 (at the origin), t2 of gear 2
mod2module, the tooth size: pitch diameter ÷ teeth. Meshing gears share it
a10how far gear 1 is turned, in degrees
dir0direction from gear 1 to gear 2, in degrees
extra0added centre distance in mm
shapes

module gear(teeth, mod = 2, pressure_angle = 20, backlash = 0.1, bore = 0, flat = 0, steps = 8)

Spur gear outline, centred on the origin.

argumentdefault
teethrequirednumber of teeth
mod2module, the tooth size: pitch diameter ÷ teeth. Meshing gears share it
pressure_angle20tooth flank angle in degrees. Meshing gears share it
backlash0.1tooth thinning in mm, the play between meshing teeth
bore0centre hole diameter (0 = none)
flat0depth of a D-flat in the bore, for a motor shaft (0 = round)
steps8points along each tooth flank (smoothness)

module rack(teeth, mod = 2, pressure_angle = 20, backlash = 0.1, base = 5)

Straight rack along +x starting at x = 0, pitch line on y = 0, teeth up, base mm of solid strip under the tooth roots. Place a gear on it with on_rack().

argumentdefault
teethrequirednumber of teeth
mod2module, the tooth size: pitch diameter ÷ teeth. Meshing gears share it
pressure_angle20tooth flank angle in degrees. Meshing gears share it
backlash0.1tooth thinning in mm, the play between meshing teeth
base5depth of solid strip under the teeth, in mm

module on_rack(teeth, mod = 2, k = 1, extra = 0)

Place children (a gear with teeth teeth) meshing with a rack, its centre above the rack's k-th gap.

argumentdefault
teethrequiredthe gear's tooth count
mod2module, the tooth size: pitch diameter ÷ teeth. Meshing gears share it
k1which gap of the rack the gear sits over (x = k × π × mod)
extra0added centre distance in mm

Curved boxes (flex) use <flex.kerf>

Curved boxes: walls that bend with living hinges and finger-joint into flat bottom and top plates.

flex_box(rounded_rect([120, 80], 20), 60, "ply3");            // rounded box
flex_box(rounded_rect([120, 80], 20), 60, "ply3", lip = 4);   // plates overhang, tabs through slots
flex_box(round_corners([[0, 0], [100, 0], [100, 70], [0, 70]], [0, 30, 30, 0]),
         50, "ply3");                                          // flat back, rounded front
flex_box([for (a = [0 : 5 : 355]) 40 * [cos(a), sin(a)]], 80, "ply3", top = "open"); // a pot

path is the OUTSIDE of the walls seen from above: a list of points, closed. Rounded stretches bend; sharp square corners split the walls into separate panels with finger joints. Everything is measured along the middle of the sheet, so the fingers on the walls land in the plates' notches after bending — no lengths to match by hand.

The built-in pieces, for your own designs (holes, engraving, other plates):

round_corners(points, r)         path with rounded corners (r per point, 0 = sharp)
flex_plate(path, t, lip = 0)      plate outline with notches (lip = 0, flush)
                                  or slots (lip > 0: plate overhangs by lip)
flex_panel(path, height, t, panel = 0, top = true)   one flat wall panel
flex_bend(path, t, panel = 0) part(...)             show it bent in 3D
flex_count(path, t), flex_lengths(path, t)          number and flat lengths of panels

Plates and panels must get the same path, t, fw, sag, margin and max_len to line up.

max_len: longest flat panel you can cut. flex_box uses the material's sheet (material(..., sheet = [w, h]) less its margins) unless you give one. Longer walls are split into panels that butt together on straight stretches; the plates' fingers hold the joint in line.

flex_box(rounded_rect([120, 80], 20), 60, "ply3", max_len = 200);

Hinge settings: pitch (line spacing), seg (cut length), bridge (uncut gap), margin (how far the cuts run past each bend). Bigger radius = easier bend. The full list of arguments is under flex_box.

function rounded_rect(size, r)

A rounded-rectangle path with its corner at the origin, to use as a box's outline.

argumentdefault
sizerequired[w, d] outside size
rrequiredcorner radius: one for all corners, or [r1, r2, r3, r4] counter-clockwise from the origin (0 = sharp)

module flex_box(path, height, material, top = "closed", lip = 0, fw = 12, sag = 0.1, pitch = 1.5, seg = 18, bridge = 3, margin = 3, max_len = undef, label = "", font = "Liberation Sans:style=Bold", label_size = 10, name = "flex")

Whole box: <name>_bottom, <name>_top (unless top = "open") and the walls <name>_wall (or _wall1, _wall2, … when sharp corners split them), placed in 3D with the bottom on z = 0. height is the outside height. Walls too long for the material's sheet are split (see max_len above).

argumentdefault
pathrequiredoutside of the walls seen from above, a closed list of [x, y] points
heightrequiredoutside height, plates included
materialrequiredmaterial name
top"closed""closed" or "open"
lip0how far the plates overhang the walls, in mm. 0 = flush (fingers in notches); more = fingers poke through slots as tabs
fw12target finger width on straight stretches
sag0.1how far a straight finger may stand off a curve, in mm: sets the finger width on curves, √(8 × radius × sag)
pitch1.5spacing between hinge cut lines; smaller bends more easily
seg18length of each hinge cut
bridge3uncut gap between hinge cuts; smaller bends more easily
margin3how far the hinge cuts run past each bend
max_lenundeflongest flat panel; default: the sheet's longer side less its margins
label""text engraved on the top
font"Liberation Sans:style=Bold"the label's font
label_size10the label's text size
name"flex"start of every part's name