pill container

This commit is contained in:
sam.hadow committed 2026-10-05 11:12:05 +02:00
1 parent 67f0d26978
commit 16e69fb10d
1 file changed
+251
+251
View File
@@ -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();
}