From 6737bd36a04651e12ef153bd206d5dbf04293989 Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Thu, 8 Oct 2026 11:46:00 +0200 Subject: [PATCH] improve clip in strength --- pill_container/pill_container.scad | 192 ++++++++++++++++------------- 1 file changed, 109 insertions(+), 83 deletions(-) diff --git a/pill_container/pill_container.scad b/pill_container/pill_container.scad index 91c9efe..fdb421e 100644 --- a/pill_container/pill_container.scad +++ b/pill_container/pill_container.scad @@ -8,12 +8,12 @@ cases = 4; // Inner size of each compartment: X * X * X (mm) X = 20; -// Text engraved on the lid +// Text engraved on the lid (runs parallel to the sliding direction) engraving = "Monday"; /* [Engraving] */ text_size = 6; -text_depth = 0.6; +text_depth = 0.6; // engraved with sloped walls (see dt_ang) text_font = "Overpass:style=Bold"; /* [Structure] */ @@ -21,33 +21,39 @@ 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) +rail_ledge = 1.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 +/* [Dovetail] */ +// Angle of the sliding faces from vertical = worst overhang of the print. +// 30 prints cleanly without supports; lower = even safer but less grip. +dt_ang = 30; // [15:1:45] + +/* [Fit] */ +// Sliding clearance between lid and box (PETG: 0.2 - 0.3) tol = 0.25; // Clearance inside the snap pockets (smaller = tighter, less rattle) snap_tol = 0.15; /* [Snap] */ +// Length of the slots in the lid = length of the flexible tab and arms. +clip_len = 11.4; // 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; +detent = 0.5; +// Side ridges: same, but sideways +side_detent = 0.5; +// Flexible tab (rear bump): width, thickness, slot width +clip_w = 5; +tab_t = 1.2; // thinner = softer (the underside of the tab is relieved) clip_slot = 1.0; -// Flexible side arms: thickness (stiffness) and slot width -arm_t = 1.6; +// Flexible side arms: thickness (stiffness) and slot width, both measured +// perpendicular to the sloped faces +arm_t = 1.2; arm_slot = 1.2; /* [Display] */ -// plate = both parts laid out for printing, assembly = closed for preview +// 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; @@ -58,6 +64,9 @@ lid_offset = 0; $fn = 48; eps = 0.01; +m = tan(dt_ang); // sideways shift per mm of height +cs = cos(dt_ang); + 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) @@ -65,35 +74,43 @@ 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 +k = tol/cs; // horizontal gap = tol on the sloped faces +lw_b = gb - m*tol - k; // lid half width at its bottom +lw_t = gb - m*hg - k; // lid half width at its top +d1 = arm_t/cs; // horizontal offsets of the arm slot +d2 = (arm_t + arm_slot)/cs; // walls from the sloped face 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 +tab_r = lid_h - tab_t; // relief under the tab (softens it) + +// Engraving: shrink the text if it would not fit between the lid front +// and the slots (average bold glyph is ~0.9 * text_size wide, conservative) +text_zone = lid_len - clip_len - 2*2; // 2 mm margin at both ends +text_fit = min(text_size, text_zone/(len(engraving)*0.9)); bump_h = tol + detent; // rear V bump height -sb_h = tol + side_detent; // side bump height +sb_h = tol + side_detent; // side ridge 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; +zm = tol + lid_h/2; // mid height of the lid faces 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(dt_ang >= 15 && dt_ang <= 45, "dt_ang must be between 15 and 45"); +assert(rail_ledge < wall - 1, "rail_ledge must be at least 1 mm smaller than wall"); +assert(m*lid_h - k >= 1, "Dovetail grip too small: increase lid_h or dt_ang"); 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"); +assert(lw_t - d2 - (clip_w/2 + clip_slot) >= 1.2, + "Tab, slots and side arms do not fit in the lid (X too small, about 18 mm minimum with default settings): reduce clip_w / clip_slot / arm_t / arm_slot or increase X"); +assert(tab_t >= 0.8 && tab_t <= lid_h, "tab_t must be between 0.8 and lid_h"); +assert(lid_len >= clip_len + 8, "Lid too short for this clip_len: reduce clip_len"); +assert(text_zone > 4, "Lid too short for any engraving: reduce clip_len"); echo(str("Outer size: ", L, " x ", W, " x ", H, " mm")); +if (text_fit < text_size) + echo(str("Engraving scaled down from size ", text_size, " to ", text_fit)); +if (text_fit < 3) + echo("WARNING: engraving is very small; use fewer characters, more cases or a shorter clip_len"); // --------------------------------------------------------------------- // Helpers @@ -102,7 +119,7 @@ echo(str("Outer size: ", L, " x ", W, " x ", H, " mm")); 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 +// Extrude a 2D (x,z) profile along Y, centred on y = 0 module xz_extrude(w) { rotate([90, 0, 0]) linear_extrude(height = w, center = true) children(); } @@ -110,27 +127,20 @@ 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); +// Side ridge profile: x = sliding direction, y = face normal. +// It runs along the whole sloped face (no end faces -> no overhang). +module side_bump_2d() { + polygon([[-sb_h, -0.3], [sb_h, -0.3], [sb_h, 0], [0, sb_h], [-sb_h, 0]]); } - +module side_prism(grow = 0) { + linear_extrude(height = hg/cs + 4, center = true) + offset(delta = grow) side_bump_2d(); +} +// Place children on the middle of the lid's sloped face 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(); + translate([xb, s*(gb - m*zm - k), z0 + zm]) + if (s > 0) rotate([dt_ang, 0, 0]) children(); + else mirror([0, 1, 0]) rotate([dt_ang, 0, 0]) children(); } // --------------------------------------------------------------------- @@ -149,8 +159,8 @@ module box() { translate([wall, 0, z0]) yz_extrude(L - wall + eps) polygon([[-gb, 0], [gb, 0], - [ gb - hg - eps, hg + eps], - [-(gb - hg - eps), hg + eps]]); + [ gb - m*(hg + eps), hg + eps], + [-(gb - m*(hg + eps)), hg + eps]]); // V groove in the rear wall for the tab bump translate([xb, 0, 0]) @@ -160,9 +170,11 @@ module box() { [ 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); + // V channels along the rail faces for the side ridges + intersection() { + union() for (s = [-1, 1]) on_side(s) side_prism(snap_tol); + translate([xb - 5, -W, z0]) cube([10, 2*W, hg + 1]); + } // lead-in chamfer so the tab bump can climb onto the rear wall xz_extrude(X) @@ -173,48 +185,65 @@ module box() { } } +// Engraving +engr_slices = 6; +module engraving_cut(tx) { + dz = text_depth/engr_slices; + for (i = [0 : engr_slices - 1]) + translate([tx, 0, z0 + hg - (i + 1)*dz]) + linear_extrude(dz + eps) + offset(delta = -i*dz*tan(dt_ang)) + text(engraving, size = text_fit, font = text_font, + halign = "center", valign = "center"); +} + // --------------------------------------------------------------------- -// Lid +// Lid (modelled in its closed position, engraved face up) // --------------------------------------------------------------------- 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; + // y of the right sloped face, moved inwards by d (horizontal), at height z + function yf(z, d) = lw_b - m*(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]]); + // lid plate (dovetail profile), underside of the tab relieved + difference() { + translate([lid_x0, 0, z0]) + yz_extrude(lid_len) + polygon([[-lw_b, tol], [lw_b, tol], + [ lw_t, hg], [-lw_t, hg]]); + translate([L - clip_len, -(clip_w/2 + clip_slot), z0 + tol - 1]) + cube([clip_len + 1, clip_w + 2*clip_slot, 1 + tab_r]); + } if (bumps) { - // V bump under the free end of the tab + // V bump under the free end of the tab (45 deg flanks, grows + // from the relieved underside, apex at the same height as before) 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); + polygon([[-(bump_h + tab_r), z0 + tol + tab_r + eps], + [ bump_h + tab_r, z0 + tol + tab_r + eps], + [ 0, z0 + tol - bump_h]]); + // side ridges, cut flush with the lid top and bottom planes + intersection() { + union() for (s = [-1, 1]) on_side(s) side_prism(0); + translate([xb - 5, -W/2, z0 + tol]) cube([10, W, lid_h]); + } } } - // slots that free the central tab + // slots that free the central tab (vertical walls) 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 + // slots that free the side arms, parallel to the sloped faces so the + // arms keep a constant thickness (same angle as the dovetail) for (s = [-1, 1]) translate([L - clip_len, 0, z0]) yz_extrude(clip_len + 1) @@ -223,10 +252,7 @@ module lid(bumps = true) { [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"); + engraving_cut(text_x); } } @@ -248,4 +274,4 @@ if (part == "plate") { } else if (part == "assembly") { box(); translate([lid_offset, 0, 0]) lid(); -} \ No newline at end of file +}