Bolt, Nut and Washer Set
A printable hex bolt, nut and flat washer in ISO metric sizes M8, M10 and M12, with real 60 degree threads. The thread is cut by sweeping a groove profile along a helix, and the nut is made by subtracting the same threaded shank enlarged by a clearance, so the bolt and nut always mesh whatever the size. Head, nut and washer dimensions follow ISO 4014, ISO 4032 and ISO 7089. The default M12 x 30 set lays out at 76 x 24 x 38 mm. Print as laid out, bolt head down and nut and washer flat; no supports are needed, and M10 or larger works best with a 0.4 mm nozzle. You can choose the size and change the bolt length, the thread clearance (0.2-0.35 mm usually works) and the washer hole clearance.
# Printable Bolt, Nut and Washer Set - ISO metric M8/M10/M12 with real threads
#
# Three-part layout along X: bolt (hex head down), nut and washer.
# The thread is cut by sweeping a groove tool along a helix and subtracting it from a round bar.
# The nut is made by subtracting a "plug" (the bolt shank enlarged by the clearance, with the same groove cut) from a hexagon.
# This way the male and female thread profiles match by construction.
#
# Dimensions: ISO 261 coarse pitch, ISO 4014/4017 hex bolt head, ISO 4032 nut,
# ISO 7089 plain washer. Widths across flats are ISO (M10=16, M12=18; JIS uses 17/19).
@param choices:["M8", "M10", "M12"] desc:"Nominal size"
size = "M12"
@param 15..60 step:1 desc:"Length under the head (mm)"
length = 30
@param 0.1..0.5 step:0.05 desc:"Radial clearance of the nut thread (mm)"
fit = 0.25
@param 0..1 step:0.1 desc:"Extra clearance for the washer hole (mm)"
washer_play = 0.2
# --- Table by nominal size -----------------------------------------
d = if size == "M8" then 8 else if size == "M10" then 10 else 12
pitch = if size == "M8" then 1.25 else if size == "M10" then 1.5 else 1.75
af = if size == "M8" then 13 else if size == "M10" then 16 else 18 # width across flats
head_h = if size == "M8" then 5.3 else if size == "M10" then 6.4 else 7.5 # k
nut_h = if size == "M8" then 6.8 else if size == "M10" then 8.4 else 10.8 # m
w_id = if size == "M8" then 8.4 else if size == "M10" then 10.5 else 13 # washer inner diameter
w_od = if size == "M8" then 16 else if size == "M10" then 20 else 24
w_t = if size == "M8" then 1.6 else if size == "M10" then 2 else 2.5
r = d / 2
hr = af / sqrt(3) # hexagon circumradius
# --- Thread groove tool profile (60 deg thread, ISO crest flat of pitch/8) ---
land = pitch / 8
hw = (pitch - land) / 2
gd = hw * sqrt(3) # groove depth of a 60 deg thread
lead = gd / 8 # tool sticks out past the surface (avoids tangent slivers)
groove = polyline [(lead, -(hw * (1 + lead / gd))),
(-gd, 0),
(lead, hw * (1 + lead / gd))]
# --- Bolt ----------------------------------------------------------
runout = 1 # start the groove 1mm below the seat, so it does not end on the head face
tip = gd + lead # 45 deg chamfer at the tip (lead-in for the nut)
shaft = cylinder r length | floor
| faces >Z | chamfer tip
| diff (helix pitch (length + 4 + runout) r
| sweep groove
| translate 0 0 (-runout - 2)
| inter (cylinder (r + 1) (length + 8) | floor | translate 0 0 (-runout)))
| translate 0 0 head_h
head = polygon 6 hr
| extrude head_h
| faces <Z | chamfer 1
bolt = union [head, shaft]
# --- Nut -----------------------------------------------------------
rn = r + fit
plug = cylinder rn (nut_h + 2) | translate 0 0 (nut_h / 2)
| diff (helix pitch (nut_h + 6) rn | sweep groove | translate 0 0 -3)
nut = polygon 6 hr
| extrude nut_h
| faces >Z | chamfer 1
| faces <Z | chamfer 1
| diff plug
# --- Washer --------------------------------------------------------
washer = circle (w_od / 2) | extrude w_t
| diff (cylinder (w_id / 2 + washer_play) (w_t + 2) | floor | translate 0 0 -1)
# --- Layout (side by side along X, 5mm between parts) ----------------
gap = 5
union [bolt,
nut | translate (hr * 2 + gap) 0 0,
washer | translate (-(hr + w_od / 2 + gap)) 0 0]