// ===================================================================== // 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(); }