Print-in-Place Hinge Box
A small box for screws, SD cards, earbuds and other bits, printed in one go together with its lid, already hinged. The hinge is a row of interleaved knuckles: each lid knuckle has a 45 deg cone on both ends that turns inside a matching socket of the neighbouring box knuckle with a 0.4 mm gap, so the pin is formed in place and the lid is captive. A U-shaped lip under the lid gives a light friction fit in the box opening, and a small overhang at the front is the finger grip for opening. The box is 60 x 40 x 27 mm with 56 x 36 x 25 mm inside, 60 x 50 x 30 mm when closed, and the print layout is 60 x 90 x 27 mm with the lid lying upside down beside the box. Print as laid out with no supports; afterwards bend the lid up firmly once to break the hinge free, then work it back and forth until it turns smoothly. You can change the inner width, depth and height, the wall thickness, the hinge clearance, the number of knuckles and the lip tightness.
# Hinge Box - box and lid with a print-in-place hinge
# Box and lid are printed together, already hinged: no pins, no glue, no assembly.
#
# Print layout (as modeled): the box stands on its bottom (y < 0) and the lid lies upside down
# beside it (y > 0), i.e. the lid is opened 180 deg. The hinge axis runs along X at y = 0,
# z = hinge_z, half the closed height, so that a 180 deg turn puts the lid exactly on the rim.
# Every knuckle is a "tombstone" (a leaf standing on the bed with a round top), so nothing
# overhangs and no supports are needed.
#
# Hinge: knuckles alternate box / lid along X with an axial gap `gap`. Each lid knuckle carries a
# 45 deg cone on both ends that sits in a conical socket of the neighbouring box knuckle, offset
# by `gap` all round. The cones are the pin: they hold the lid radially and the outer box
# knuckles hold it axially, so the hinge is captive.
# Catch: a U-shaped friction lip under the lid slides into the box opening (front and sides).
# It stops short of the back wall, because points near the hinge swing through the back wall.
#
# After printing: hold the box and bend the lid up firmly once to break any stringing in the
# hinge gaps, then work it back and forth a few times until it turns freely. Close the lid by
# swinging it over onto the box and pressing it into the rim; open it with the front overhang.
@param 30..120 desc:"Inner width, along the hinge (mm)" group:"Size"
inner_w = 56
@param 20..80 desc:"Inner depth, front to back (mm)" group:"Size"
inner_d = 36
@param 10..50 desc:"Inner height of the box (mm)" group:"Size"
inner_h = 25
@param 1.6..3 step:0.2 desc:"Wall and floor thickness (mm)" group:"Size"
wall = 2.0
@param 0.3..0.6 step:0.05 desc:"Hinge moving clearance, radial and axial (mm)" group:"Hinge"
gap = 0.4
@param 3..9 step:2 desc:"Number of hinge knuckles (odd: box knuckles at both ends)" group:"Hinge"
knuckles = 5
@param 0.1..0.3 step:0.05 desc:"Lid lip clearance per side, smaller = tighter (mm)" group:"Catch"
lip_gap = 0.15
# ---- derived sizes ----------------------------------------------------------
W = inner_w + 2 * wall # outer width (X)
D = inner_d + 2 * wall # outer depth of the box body (Y)
H = inner_h + wall # box height (Z)
lid_t = 3 # lid plate thickness
hinge_z = (H + lid_t) / 2 # hinge axis height: half the closed height
R = 4 # knuckle radius
rc = 2.5 # cone pin base radius (45 deg cone)
e = 0.5 # overlap of cones into their knuckle
ov = 1.5 # lid overhang at the front, a finger grip
lip_t = 1.2 # lip wall thickness
lip_h = 3 # lip height
lip_back = 6 # lip ends this far from the back wall
yb = -(R + gap) # box back face (lid back edge is at -yb)
seg = (W - (knuckles - 1) * gap) / knuckles # knuckle length along X
s2 = sqrt(2)
def slab($x0, $x1, y0, y1, z0, z1) = box ($x1 - $x0) (y1 - y0) (z1 - z0) | translate (($x0 + $x1) / 2) ((y0 + y1) / 2) ((z0 + z1) / 2)
def xcyl(r, $x0, $x1) = cylinder r ($x1 - $x0) | rotate 0 90 0 | translate (($x0 + $x1) / 2) 0 hinge_z
# 45 deg cone on the hinge axis: base radius r at x = xb, apex at xb + r (pos) or xb - r (neg)
def cone_pos(r, xb) = cone r 0 r | rotate 0 90 0 | translate (xb + r / 2) 0 hinge_z
def cone_neg(r, xb) = cone 0 r r | rotate 0 90 0 | translate (xb - r / 2) 0 hinge_z
def x0(i) = -W / 2 + i * (seg + gap)
def x1(i) = x0(i) + seg
# knuckle: a leaf from the bed up to the axis with a round top
def tomb(i) = slab(x0(i), x1(i), -R, R, 0, hinge_z) + xcyl(R, x0(i), x1(i))
box_idx = [2 * k for k in range(ceil(knuckles / 2))]
lid_idx = [2 * k + 1 for k in range(floor(knuckles / 2))]
# ---- box (y < 0) --------------------------------------------------------------
$shell = slab(-W / 2, W / 2, yb - D, yb, 0, H) | edges =Z <Y | fillet 3
cavity = slab(-inner_w / 2, inner_w / 2, yb - D + wall, yb - wall, wall, H + 1)
box_knuckles = union [tomb(i) + slab(x0(i), x1(i), yb - 1, 0, 0, hinge_z) for i in box_idx]
# conical sockets: the lid cones grown by `gap` (45 deg, so shifted gap*sqrt2 along the axis)
sockets = union [cone_neg(rc + gap * s2 + e, x0(i) + e) + cone_pos(rc + gap * s2 + e, x1(i) - e) for i in lid_idx]
$box = ($shell - cavity + box_knuckles) - sockets
# ---- lid (y > 0, upside down) -------------------------------------------------
plate = slab(-W / 2, W / 2, -yb, -yb + D + ov, 0, lid_t) | edges =Z >Y | fillet 3
lip_y0 = -yb + wall + lip_back
lip_y1 = -yb + D - wall - lip_gap
lip_x = inner_w / 2 - lip_gap
lip = slab(-lip_x, lip_x, lip_y0, lip_y1, lid_t, lid_t + lip_h)
- slab(-lip_x + lip_t, lip_x - lip_t, lip_y0 - 1, lip_y1 - lip_t, lid_t, lid_t + lip_h + 1)
lid_knuckles = union [tomb(i) + slab(x0(i), x1(i), 0, -yb + 1, 0, lid_t)
+ cone_neg(rc + e, x0(i) + e) + cone_pos(rc + e, x1(i) - e) for i in lid_idx]
lid = plate + lip + lid_knuckles
# ---- checks ---------------------------------------------------------------------
def swing(a) = lid | rotate a 0 0 origin:(0, 0, hinge_z)
closed = $box + swing(180)
$print = $box + lid
assert knuckles % 2 == 1
assert $box.solids == 1
assert lid.solids == 1
assert $print.solids == 2 # box and lid stay separate: the hinge is not fused
assert clear($box, lid) # print layout
assert clear($box, swing(45)) # the lid swings without hitting the box
assert clear($box, swing(90))
assert clear($box, swing(135))
assert clear($box, swing(170))
assert clear($box, swing(180)) # closed: lid rests on the rim, lip inside
assert closed.bbox.x ~ W
assert closed.bbox.z ~ H + lid_t
assert $print.bbox.z ~ H
print "print layout" $print.bbox
print "closed" closed.bbox
print "hinge axis z" hinge_z
print "knuckle length" seg
$print