pill container
This commit is contained in:
1 parent
67f0d26978
commit
16e69fb10d
1 file changed
+251
@@ -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();
|
||||
}
|
||||
Reference in new issue
Block a user