From 16e69fb10d086425e0020481ab90e4beb5f94c2b Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Mon, 5 Oct 2026 11:12:05 +0200 Subject: [PATCH] pill container --- pill_container/pill_container.scad | 251 +++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 pill_container/pill_container.scad diff --git a/pill_container/pill_container.scad b/pill_container/pill_container.scad new file mode 100644 index 0000000..91c9efe --- /dev/null +++ b/pill_container/pill_container.scad @@ -0,0 +1,251 @@ +// ===================================================================== +// Pill box with dovetail sliding lid and triple snap-in detent +// Designed for printing with PETG +// ===================================================================== + +/* [Main] */ +// Number of compartments +cases = 4; +// Inner size of each compartment: X * X * X (mm) +X = 20; +// Text engraved on the lid +engraving = "Monday"; + +/* [Engraving] */ +text_size = 6; +text_depth = 0.6; +text_font = "Overpass:style=Bold"; + +/* [Structure] */ +wall = 3.2; // outer side walls and front wall +divider = 1.6; // wall between compartments +floor_t = 1.6; // floor thickness +lid_h = 3.0; // lid thickness +rail_ledge = 2.4; // width of the ledge the lid edges rest on (< wall) +end_wall = 5.5; // rear wall thickness (holds the clip groove) +corner_r = 1.5; // vertical corner rounding + +/* [Tolerances] */ +// Sliding clearance between lid and box +tol = 0.25; +// Clearance inside the snap pockets (smaller = tighter, less rattle) +snap_tol = 0.15; + +/* [Snap] */ +// Rear bump: how far it is pushed aside while sliding (click strength) +detent = 0.7; +// Side bumps: same, but sideways +side_detent = 0.6; +// Side bump length along the lid face +side_len = 1.8; +// Flexible tab (rear bump): width, max length, slot width +clip_w = 4.5; +clip_len_max = 28; +clip_slot = 1.0; +// Flexible side arms: thickness (stiffness) and slot width +arm_t = 1.6; +arm_slot = 1.2; + +/* [Display] */ +// plate = both parts laid out for printing, assembly = closed for preview +part = "plate"; // [plate, box, lid, assembly] +// Assembly preview only: push the lid open by this many mm +lid_offset = 0; + +// --------------------------------------------------------------------- +// Derived values +// --------------------------------------------------------------------- +$fn = 48; +eps = 0.01; + +W = X + 2*wall; // outer width (Y) +x_cav_end = wall + cases*X + (cases-1)*divider; // end of last compartment +L = x_cav_end + end_wall; // outer length (X) +z0 = floor_t + X; // top of compartments +hg = lid_h + tol; // height of the lid groove +H = z0 + hg; // total height +gb = X/2 + rail_ledge; // groove half width at z0 +k45 = tol*sqrt(2); // horizontal offset = tol on a 45 deg face +lw_b = gb - tol - k45; // lid half width at its bottom +lw_t = gb - hg - k45; // lid half width at its top + +lid_x0 = wall + tol; // lid front (closed position) +lid_len = L - lid_x0; // lid rear is flush with box rear +clip_len = min(clip_len_max, lid_len*0.45); // tab and arm length + +bump_h = tol + detent; // rear V bump height +sb_h = tol + side_detent; // side bump height +cham = detent + tol; // lead-in chamfer on rear wall +xb = x_cav_end + (end_wall - cham)/2; // x of all detents + +// Height (above z0) of the side bump centre on the sloped lid face, chosen so that bump and dimple stay clear of the groove bottom and of the rail top +sb_hl = side_len/2 + snap_tol; +sb_up = sqrt(0.5)*(sb_hl + sb_h + snap_tol); +sb_dn = sqrt(0.5)*(sb_hl + 0.2 + snap_tol); +zs_lo = 0.15 + sb_dn; +zs_hi = hg - 0.35 - sb_up; +zs = (zs_lo + zs_hi)/2; + +assert(cases >= 1, "cases must be >= 1"); +assert(rail_ledge < wall, "rail_ledge must be smaller than wall"); +assert(zs_lo <= zs_hi, "Lid too thin for the side bumps: increase lid_h or reduce side_len / side_detent"); +assert(arm_slot > side_detent + 0.2, "arm_slot must exceed side_detent + 0.2"); +assert(clip_w/2 + clip_slot + 1.2 < lw_t - (arm_t + arm_slot)*sqrt(2), + "Tab, slots and side arms do not fit in the lid (X is too small, about 20 mm minimum with default settings): decrease clip_w / clip_slot / arm_t / arm_slot or increase X"); + +echo(str("Outer size: ", L, " x ", W, " x ", H, " mm")); + +// --------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------- +// Extrude a 2D (y,z) profile along +X +module yz_extrude(len) { + rotate([90, 0, 90]) linear_extrude(height = len) children(); +} +// Extrude a 2D (x,z) profile along Y, centered on y = 0 +module xz_extrude(w) { + rotate([90, 0, 0]) linear_extrude(height = w, center = true) children(); +} +module rrect(l, w, r) { + translate([0, -w/2]) offset(r = r) offset(delta = -r) square([l, w]); +} + +// Side bump +module side_bump_3d(extra = 0) { + h = sb_h + extra; + len = side_len + extra*2; + + // Pyramid pointing outwards in local +Y + rotate([-90, 0, 0]) + scale([1, len/(2*h), 1]) + cylinder(h = h, r1 = h, r2 = 0, $fn = 4); + + // Embedded base + translate([0, -0.2, 0]) + rotate([-90, 0, 0]) + scale([1, len/(2*h), 1]) + cylinder(h = 0.2, r1 = h, r2 = h, $fn = 4); +} + +module on_side(s) { // s = +1 right side, -1 left side + translate([xb, s*(lw_b - (zs - tol)), z0 + zs]) + if (s > 0) rotate([45, 0, 0]) children(); + else mirror([0, 1, 0]) rotate([45, 0, 0]) children(); +} + +// --------------------------------------------------------------------- +// Box +// --------------------------------------------------------------------- +module box() { + difference() { + linear_extrude(H) rrect(L, W, corner_r); + + // compartments + for (i = [0 : cases-1]) + translate([wall + i*(X + divider), -X/2, floor_t]) + cube([X, X, X + eps]); + + // dovetail groove for the lid (open at the rear end) + translate([wall, 0, z0]) + yz_extrude(L - wall + eps) + polygon([[-gb, 0], [gb, 0], + [ gb - hg - eps, hg + eps], + [-(gb - hg - eps), hg + eps]]); + + // V groove in the rear wall for the tab bump + translate([xb, 0, 0]) + xz_extrude(clip_w + 2*snap_tol) + offset(delta = snap_tol) + polygon([[-bump_h, z0 + tol], + [ bump_h, z0 + tol], + [ 0, z0 + tol - bump_h]]); + + // dimples in the rails for the side bumps + for (s = [-1, 1]) on_side(s) + side_bump_3d(snap_tol); + + // lead-in chamfer so the tab bump can climb onto the rear wall + xz_extrude(X) + polygon([[L - cham, z0], + [L + 1, z0 - cham - 1], + [L + 1, z0 + 1], + [L - cham, z0 + 1]]); + } +} + +// --------------------------------------------------------------------- +// Lid +// --------------------------------------------------------------------- +module lid(bumps = true) { + text_x = (lid_x0 + L - clip_len)/2; + d1 = arm_t*sqrt(2); + d2 = (arm_t + arm_slot)*sqrt(2); + za = tol - 1; + zb = hg + 1; + // x-offset of the side face at height z (right side) + function yf(z, d) = lw_b - (z - tol) - d; + + difference() { + union() { + // lid plate (dovetail profile) + translate([lid_x0, 0, z0]) + yz_extrude(lid_len) + polygon([[-lw_b, tol], [lw_b, tol], + [ lw_t, hg], [-lw_t, hg]]); + + if (bumps) { + // V bump under the free end of the tab + translate([xb, 0, 0]) + xz_extrude(clip_w) + polygon([[-bump_h, z0 + tol + eps], + [ bump_h, z0 + tol + eps], + [ 0, z0 + tol - bump_h]]); + + // side bumps on the flexible arms + for (s = [-1, 1]) on_side(s) + side_bump_3d(0); + } + } + + // slots that free the central tab + for (s = [-1, 1]) + translate([L - clip_len, + s*(clip_w/2 + clip_slot/2) - clip_slot/2, + z0 - 1]) + cube([clip_len + 1, clip_slot, hg + 2]); + + // slots (parallel to the sloped faces) that free the side arms + for (s = [-1, 1]) + translate([L - clip_len, 0, z0]) + yz_extrude(clip_len + 1) + scale([s, 1]) + polygon([[yf(za, d1), za], [yf(zb, d1), zb], + [yf(zb, d2), zb], [yf(za, d2), za]]); + + // engraving, parallel to the sliding direction + translate([text_x, 0, z0 + hg - text_depth]) + linear_extrude(text_depth + eps) + text(engraving, size = text_size, font = text_font, + halign = "center", valign = "center"); + } +} + +// Lid turned upside down, engraved face on the bed (z = 0) +module lid_print() { + translate([0, 0, z0 + hg]) rotate([180, 0, 0]) lid(); +} + +// --------------------------------------------------------------------- +// Output +// --------------------------------------------------------------------- +if (part == "plate") { + box(); + translate([0, W/2 + lw_b + 8, 0]) lid_print(); +} else if (part == "box") { + box(); +} else if (part == "lid") { + lid_print(); +} else if (part == "assembly") { + box(); + translate([lid_offset, 0, 0]) lid(); +} \ No newline at end of file