Living-Hinge Carabiner
A one-piece accessory carabiner for keys, a water bottle or a bag strap: an oval frame with a gate that is a thin leaf spring grown out of the frame. Push the gate inward and it bends about its root, the tip drops below the nose and the opening is free; let go and it springs back under a lip that keeps it from opening outward. The leaf bends in the layer plane, so the hinge never loads the layer bond, and the model itself checks that a 6 mm ring or strap passes the open gate and that the tip overlaps the nose by 3 mm when closed. It measures 70 x 40 x 5 mm and prints flat on its side in one piece with no supports; PETG or another tough filament is recommended for the hinge. Frame length and width, bar width, thickness, gate leaf thickness, tip overlap, opening angle and ring size are all parameters. This is not a climbing carabiner: not for climbing or any load-bearing use.
# Accessory Carabiner - one-piece oval frame with a living-hinge leaf-spring gate
#
# NOT load rated: for keys, a water bottle, a bag strap. Never for climbing or
# any load-bearing use.
#
# One flat part, printed on its side (the XY profile extruded `thick` along Z).
# Frame: a rounded-rectangle ring, `bar` wide in the plane and `thick` high,
# with its top and bottom edges filleted for comfort in the hand.
# Gate: a thin straight leaf (gate_t thick, full print height) that grows out
# of the frame at the left end of the opening (x = xa). The leaf itself
# is the living hinge and the spring: pushing it inward bends it about
# its root, the tip drops below the nose and the opening is free; let go
# and it springs back. The gate is recessed under the frame outline, so
# nothing catches on it.
# Nose: the right end of the opening. A lip (lip_t thick) continues the outer
# frame surface over the gate tip by `overlap`, so a ring pulling the
# gate outward from inside is stopped by the lip, not by the hinge.
# Bending happens in the layer plane, so the hinge never loads the layer bond.
# Origin: frame center. Print orientation = as modelled (z = 0 on the bed).
@param 65..110 step:5 desc:"Frame length, outer (mm)"
length = 70
@param 32..60 step:2 desc:"Frame width, outer (mm)"
width = 40
@param 6..10 step:0.5 desc:"Frame bar width in the plane (mm)"
bar = 8
@param 3..8 step:0.5 desc:"Frame thickness = print height (mm)"
thick = 5
@param 0.8..1.2 step:0.1 desc:"Gate leaf / living hinge thickness (mm)"
gate_t = 1.0
@param 2..5 step:0.5 desc:"Gate tip overlap under the nose lip (mm)"
overlap = 3
@param 18..30 step:1 desc:"Gate opening angle checked (deg, tip pushed inward; raise it for a short frame)"
open_deg = 20
@param 4..8 step:0.5 desc:"Ring / strap thickness that must pass the open gate (mm)"
ring_d = 6
L = length
W = width
corner_r = min(10, W / 2 - 2) # outer corner radius of the frame
lip_t = 1.6 # nose lip thickness (radial)
cv = 0.4 # clearance between the gate tip and the lip underside
c = 0.6 # clearance between the gate tip end and the nose face
nose_len = 15 # length of the nose block from the opening to the frame end
embed = 3 # how far the gate root reaches into the frame bar
edge_r = min(1.2, thick / 2 - 0.5) # comfort fillet on the frame's top and bottom edges
# --- Key x positions along the top bar (gate side, y = +W/2) -----------------
xa = -L / 2 + corner_r # gate root: where the left corner arc meets the straight bar
xb = L / 2 - nose_len # nose face
x_tip = xb - c # gate tip end
x_lip = x_tip - overlap # left edge of the nose lip
Lg = x_tip - xa # free gate length (the cantilever)
y_g = W / 2 - lip_t - cv - gate_t / 2 # gate center line
y_gt = y_g + gate_t / 2 # gate outer face
y_gb = y_g - gate_t / 2 # gate inner face (the hinge pivots about its root here)
y_in = W / 2 - bar # inner face of the top bar = nose underside
# --- Frame ---------------------------------------------------------------------
outer2d = rect L W | fillet corner_r
inner2d = rect (L - 2 * bar) (W - 2 * bar) | fillet (corner_r - bar)
ring = outer2d - inner2d | extrude thick
ring_f = ring | edges =XY | fillet edge_r
assert ring_f.faces > ring.faces # the fillet took the top/bottom edges
assert ring_f.volume < ring.volume
# Opening: the top bar is removed between xa and xb, except for the lip.
opening2d = rect (xb - xa) (bar + 2) at:((xa + xb) / 2, W / 2 - bar / 2)
lip2d = rect (xb - x_lip) (lip_t + 1) at:((x_lip + xb) / 2, W / 2 - lip_t + (lip_t + 1) / 2)
cutter = opening2d - lip2d | extrude (thick + 2) | translate 0 0 -1
frame = ring_f - cutter
assert frame.solids == 1
# --- Gate: a straight leaf with a small thumb pad ------------------------------
pad_len = 10
pad_h = 1.4
x_pad = xa + 0.6 * Lg
pad2d = rect pad_len pad_h at:(x_pad, y_gt + pad_h / 2 - 0.2) | fillet 0.6
leaf2d = rect (Lg + embed) gate_t at:((xa - embed + x_tip) / 2, y_g)
gate2d = leaf2d + pad2d
gate = gate2d | extrude thick
# The free part only (root face at xa): used for the overlap and the open-state checks
free2d = rect Lg gate_t at:((xa + x_tip) / 2, y_g)
gate_free = free2d + pad2d | extrude thick
assert y_gt + pad_h - 0.2 < W / 2 - 0.3 # the pad stays under the frame outline
carabiner = frame + gate
assert carabiner.solids == 1
assert carabiner.bbox.x ~ L
assert carabiner.bbox.y ~ W
assert carabiner.bbox.z ~ thick
assert carabiner.volume ~ (frame.volume + gate_free.volume) tol:0.1% # the leaf only adds, no hidden overlap
# --- Check 1: closed state. The tip sits under the lip by `overlap`, with clearance ---
shadow = box (xb - x_lip) W (thick + 2) | translate ((x_lip + xb) / 2) (W / 4) (thick / 2)
tip_under_lip = gate_free * shadow
assert tip_under_lip.bbox.x ~ overlap tol:0.01
assert clear(frame, gate_free) # tip touches nothing: cv under the lip, c to the nose
lip_probe = box (xb - x_lip + 5) 0.5 (thick + 2) | translate ((x_lip - 5 + xb) / 2) (W / 2 - lip_t + 0.25) (thick / 2)
assert (frame * lip_probe).bbox.x ~ (xb - x_lip) tol:0.01 # the lip really is there, over the tip
span_h = W / 2 - lip_t - cv / 2 - y_in # the opening below the lip
closed_span = carabiner * (box (xb - xa - c / 2) span_h (thick + 2) | translate ((xa + xb - c / 2) / 2) (y_in + span_h / 2) (thick / 2))
assert closed_span.bbox.x ~ Lg tol:0.01 # the gate spans the whole opening when closed
# --- Check 2: living hinge thickness, measured on the finished part at the root ---
hinge_probe = box 2 (bar + 2) (thick + 2) | translate (xa + 1.5) (W / 2 - bar / 2) (thick / 2)
hinge = carabiner * hinge_probe
assert hinge.bbox.y ~ gate_t tol:0.01
assert hinge.bbox.z ~ thick tol:0.01
# --- Check 3: open state. The gate rotated inward by open_deg about its root clears the frame ---
gate_open = gate_free | rotate 0 0 (-open_deg) origin:(xa, y_gb, 0)
assert clear(gate_open, frame)
# Tip center after the rotation, and the mouth between it and the nose's inner corner
tx = xa + Lg * cos(open_deg) + (gate_t / 2) * sin(open_deg)
ty = y_gb - Lg * sin(open_deg) + (gate_t / 2) * cos(open_deg)
nx = xb
ny = y_in
mouth = sqrt((nx - tx) ** 2 + (ny - ty) ** 2) - gate_t / 2
assert mouth >= ring_d + 1
# A ring_d thick strap swept straight through the mouth, from outside the gate to inside the frame
mx = (nx + tx) / 2
my = (ny + ty) / 2
ux = (ny - ty) / (mouth + gate_t / 2)
uy = -(nx - tx) / (mouth + gate_t / 2)
path = wire [(mx - 6 * ux, my - 6 * uy), (mx + 4 * ux, my + 4 * uy)]
| offset (ring_d / 2)
| extrude (thick + 2)
| translate 0 0 -1
open_asm = frame + gate_open
assert clear(open_asm, path)
strain = 1.5 * gate_t * sin(open_deg) / Lg * 100
print "outer" L "x" W "x" thick "bar" bar "gate length" Lg
print "mouth when open (mm)" mouth "tip drop (mm)" (Lg * sin(open_deg)) "root strain (%)" strain
carabiner