// ===================================================================== // Parametric box with clip-in lid // ===================================================================== // PRINT NOTES // * Box: print open side UP. Lid: print flat (either face down). // * No supports needed - every overhang is <= 45 degrees. // * The snap beams flex in the XY plane, i.e. along the layer lines, // so they are strong (no bending across weak layer bonds). // * Suggested: 0.4 mm nozzle, 0.2 mm layers, 3+ perimeters, PLA/PETG. // Walls and beams are multiples of 0.4 mm -> full-width extrusions. // * Lid too tight? raise `clearance` (0.1 steps). // * Lid rattles? raise `snap_preload` (up to ~= clearance). // * Lid pops off too easily? raise `snap_depth` (0.1 steps) or `flex_t`. // ===================================================================== /* [Box size - outer dimensions in mm] */ length = 50; // X width = 50; // Y height = 30; // Z /* [Walls] */ wall = 2.4; // side wall thickness floor_t = 1.6; // floor thickness corner_r = 3; // rounded vertical edges (0 = sharp) foot_chamfer = 0.4; // bottom edge chamfer, compensates "elephant foot" (0 = off) /* [Lid] */ lid_t = 3; // lid thickness ledge = 0.8; // width of the shelf the lid rests on clearance = 0.2; // gap between lid and box /* [Snap fit] */ snap_depth = 0.5; // how far the ridge sticks out snap_flat = 0.6; // flat tip of the ridge snap_preload = 0.15; // 0 = loose, for snug fit clip_count = 1; // clips per long edge clip_len = 30; // length of each flexible beam flex_t = 1.6; // beam thickness (thinner = softer clip) slot_w = 1.2; // slot width = room for the beam to flex /* [Options] */ pry_notch = true; // small notch to get a fingernail under the lid notch_r = 5; part = "print"; // [print, box, lid, assembly] cutaway = false; $fn = 48; // --------------------------------------------------------------------- // Derived values // --------------------------------------------------------------------- R = corner_r; rim_wall = wall - ledge; // wall thickness in the lid rebate rr = max(R - rim_wall, 0); // rebate corner radius rl = length - 2*rim_wall; // rebate size rw = width - 2*rim_wall; lid_l = rl - 2*clearance; // lid size lid_w = rw - 2*clearance; lid_r = max(rr - clearance, 0); // The clips go on the two long edges of the lid. long_x = length >= width; edge_l = long_x ? lid_l : lid_w; // length of the clip edges edge_w = long_x ? lid_w : lid_l; // lid size across the clip edges hb = snap_depth + snap_flat/2; // half-height of ridge at its base avail = edge_l - 2*lid_r - 4; // straight edge usable for clips seg = avail / clip_count; clip_l = min(clip_len, seg - 4); ridge_l = clip_l - slot_w; clip_x = [for (i = [0 : clip_count-1]) -avail/2 + seg*(i + 0.5)]; assert(corner_r >= 0 && corner_r <= min(length, width)/2 - 0.1, "corner_r is too large for this box"); assert(height >= floor_t + lid_t + 8, "height is too small for floor + lid"); assert(rim_wall - snap_depth >= 0.8, "Rim wall too thin at the groove: reduce ledge/snap_depth or increase wall"); assert(lid_t/2 - snap_preload - hb >= 0.2, "Lid too thin for the snap ridge: increase lid_t or reduce snap_depth/snap_flat"); assert(edge_w/2 - flex_t - slot_w >= 3, "Box too narrow for the flex slots: reduce flex_t/slot_w"); assert(clip_count >= 1 && clip_l >= 8, "Box too short for this many clips: reduce clip_count"); // --------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------- module rrect(l, w, r) { // centred rounded rectangle (2D) if (r > 0.01) offset(r = r) square([l - 2*r, w - 2*r], center = true); else square([l, w], center = true); } // --------------------------------------------------------------------- // BOX // --------------------------------------------------------------------- module outer_shell() { if (foot_chamfer > 0) hull() { linear_extrude(0.01) rrect(length - 2*foot_chamfer, width - 2*foot_chamfer, max(R - foot_chamfer, 0)); translate([0, 0, foot_chamfer]) linear_extrude(height - foot_chamfer) rrect(length, width, R); } else linear_extrude(height) rrect(length, width, R); } // Snap groove running around the rebate wall. // Trapezoid profile with 45 degree flanks -> prints without supports. module snap_groove() { zc = height - lid_t/2 - snap_preload; ht = snap_flat/2; hull() { translate([0, 0, zc - ht]) linear_extrude(2*ht) rrect(rl + 2*snap_depth, rw + 2*snap_depth, rr + snap_depth); translate([0, 0, zc - hb]) linear_extrude(2*hb) rrect(rl, rw, rr); } } module pry_notch() { p = long_x ? [length/2, 0] : [0, width/2]; // on a short wall translate([p[0], p[1], height - lid_t - 1]) cylinder(r = notch_r, h = lid_t + 2); } module box() { difference() { outer_shell(); // main cavity translate([0, 0, floor_t]) linear_extrude(height) rrect(length - 2*wall, width - 2*wall, max(R - wall, 0)); // rebate for the lid (leaves a ledge of width `ledge`) translate([0, 0, height - lid_t]) linear_extrude(lid_t + 1) rrect(rl, rw, rr); snap_groove(); if (pry_notch) pry_notch(); } } // --------------------------------------------------------------------- // LID // --------------------------------------------------------------------- // Ridge along +Y edge, centred on x = 0, z = 0. 45 degree flanks. module ridge(len) { e = 0.05; ht = snap_flat/2; rotate([90, 0, 90]) translate([0, 0, -len/2]) linear_extrude(len) polygon([[-e, -hb - e], [snap_depth, -ht], [snap_depth, ht], [-e, hb + e]]); } // Rounded slot module slot(len) { hull() for (x = [-1, 1]) translate([x*(len - slot_w)/2, 0, 0]) cylinder(d = slot_w, h = lid_t + 2); } module lid_core(L, W) { difference() { union() { linear_extrude(lid_t) rrect(L, W, lid_r); for (s = [-1, 1]) scale([1, s, 1]) for (cx = clip_x) translate([cx, W/2, lid_t/2]) ridge(ridge_l); } for (s = [-1, 1]) scale([1, s, 1]) for (cx = clip_x) translate([cx, W/2 - flex_t - slot_w/2, -1]) slot(clip_l); } } module lid() { rotate([0, 0, long_x ? 0 : 90]) lid_core(edge_l, edge_w); } // --------------------------------------------------------------------- // OUTPUT // --------------------------------------------------------------------- module assembly() { big = 10 * max(length, width, height); difference() { union() { box(); color("orange") translate([0, 0, height - lid_t]) lid(); } if (cutaway) translate([-big/2, 0, -1]) cube([big, big, big]); } } if (part == "box") box(); else if (part == "lid") lid(); else if (part == "assembly") assembly(); else { // "print": both parts side by side lid_ext_y = long_x ? lid_w : lid_l; box(); translate([0, (width + lid_ext_y)/2 + 10, 0]) lid(); }