// ===================================================================== // ESP32-S3-DevKitC-1 - two-part snap-fit case // // BASE : floor + walls. Board rests on 4 round posts, on the inner side of the pin slots, lined up in X with the first and last pin. Pin rows poke down into two floor slots, so Dupont connectors plug in from underneath. // LID : cap with a skirt that clips over the base's inner "tongue". Has the two button holes. USB-C opening is split between both parts, so neither part needs a bridged (unsupported) cut-out. // // // !! Measure your board and check the values marked [CHECK] !! // Set show_board = true and part = "assembled" to preview the fit. // ===================================================================== part = "print"; // "print" | "base" | "lid" | "assembled" show_board = true; // ghost board in the "assembled" view explode = 0; // mm - lifts the lid in the "assembled" view $fn = 64; /* ---------------- Board ---------------- */ pcb_l = 63.74; // [CHECK] PCB length (USB-C end -> antenna end) pcb_w = 28.35; // [CHECK] PCB width pcb_t = 1.6; // PCB thickness pin_below = 11; // [CHECK] PCB underside -> pin tips (header + pin) header_pins = 22; // pins per row pin_pitch = 2.54; row_dist = 24.86; // distance between the two pin rows header_x0 = 2; // [CHECK] first pin CENTRE, measured from the USB end comp_h = 3.5; // tallest part above PCB top /* ---------------- USB-C opening ---------------- */ usb_slot_w = 24; // one wide slot for both connectors + cable plug overmolds usb_plug_h = 6.5; // slot height (plug overmold) usb_axis_h = 1.7; // connector axis height above PCB top /* ---------------- Buttons (lid holes) ---------------- */ // [CHECK] position of each button centre: [distance from USB end edge, offset from board centre line] boot_pos = [28, -5.68]; rst_pos = [33, -5.68]; btn_d = 3.4; // hole diameter (use a pin / paper-clip / toothpick) /* ---------------- Case / print tuning ---------------- */ wall = 2.8; // outer wall tongue_t = 1.6; // inner lip of the base (the part that carries the snap groove) tol = 0.2; // sliding clearance for PETG clr = 0.4; // board-to-wall clearance floor_t = 2.0; plate_t = 2.0; // lid top thickness top_gap = 0.6; // clearance above the tallest component overlap = 6; // how far the lid skirt slips over the base snap_d = 0.5; // snap depth - raise to 0.6 for a firmer click snap_len = 12; // length of each snap bump r_in = 1.0; // inner corner radius pin_win_w = 3.0; // floor slot width under each pin row (Dupont housing ~2.6) pin_win_margin = 2.0; // slot overhang beyond first/last pin post_d = 3; post_y = 8; // post offset from centre line (inner side of the pin rows, between the header bodies) // post X positions: in line with the first and last pin of the rows post_x = [header_x0, header_x0 + (header_pins-1)*pin_pitch]; /* ---------------- Derived ---------------- */ L_i = pcb_l + 2*clr; W_i = pcb_w + 2*clr; z_pcb_bot = pin_below + 0.5; // pin tips end 0.5 mm above the print bed z_pcb_top = z_pcb_bot + pcb_t; z_split = z_pcb_top + 2; // joint between base and lid z_ceil = z_pcb_top + comp_h + top_gap; z_top = z_ceil + plate_t; z_usb_bot = z_pcb_top + usb_axis_h - usb_plug_h/2; z_usb_top = z_pcb_top + usb_axis_h + usb_plug_h/2; z_snap = z_split - 3.3; ridge_d = snap_d + tol - 0.1; snap_x = [0.27*L_i, 0.73*L_i]; /* ---------------- Helpers ---------------- */ module rr(x0, x1, y0, y1, r) { translate([x0+r, y0+r]) offset(r=r) square([x1-x0-2*r, y1-y0-2*r]); } module outline(d=0) { rr(-d, L_i+d, -W_i/2-d, W_i/2+d, r_in+d); } module slab(d, z0, z1) { translate([0,0,z0]) linear_extrude(z1-z0) outline(d); } // 45-degree triangular prism along X; apex points toward -Y (into the case) module wedge(len, d) { rotate([90,0,90]) linear_extrude(len, center=true) polygon([[-d,0],[0.3,d+0.3],[0.3,-d-0.3]]); } // all snap positions: two per long side + one on the far end wall module snap_set(off, d) { for (xc = snap_x, s = [-1,1]) translate([xc, s*(W_i/2 + tongue_t + off), z_snap]) mirror([0, s<0 ? 1 : 0, 0]) wedge(snap_len, d); translate([L_i + tongue_t + off, 0, z_snap]) rotate([0,0,-90]) wedge(snap_len, d); } /* ---------------- BASE ---------------- */ module base() { difference() { union() { difference() { slab(wall, 0, z_split); slab(0, floor_t, z_split+1); // pocket translate([0,0,z_split-overlap]) // rebate -> leaves the tongue linear_extrude(overlap+1) difference() { outline(wall+1); outline(tongue_t); } snap_set(0, snap_d); // snap grooves } for (px = post_x, s = [-1,1]) // PCB support posts (in line with first / last pin) translate([clr+px, s*post_y, floor_t-0.01]) cylinder(d=post_d, h=z_pcb_bot-floor_t+0.01); } // pin-row slots through the floor for (s = [-1,1]) translate([clr + header_x0 - pin_win_margin, s*row_dist/2 - pin_win_w/2, -1]) cube([(header_pins-1)*pin_pitch + 2*pin_win_margin, pin_win_w, floor_t+2]); // lower half of the USB-C opening (open at the top -> no bridging) translate([-wall-1, -usb_slot_w/2, z_usb_bot]) cube([wall+1, usb_slot_w, z_split+1-z_usb_bot]); } } /* ---------------- LID (modelled in assembled position) ---------------- */ module lid() { difference() { union() { slab(wall, z_split, z_top); // cap translate([0,0,z_split-overlap+0.3]) // skirt over the tongue linear_extrude(overlap-0.3+0.01) difference() { outline(wall); outline(tongue_t+tol); } snap_set(tol, ridge_d); // snap ridges } slab(0, z_split-1, z_ceil); // cavity // upper half of the USB-C opening (open at the skirt edge -> no bridging) translate([-wall-1, -usb_slot_w/2, z_split-overlap-1]) cube([wall+1, usb_slot_w, z_usb_top-(z_split-overlap-1)]); // BOOT / RST holes for (p = [boot_pos, rst_pos]) translate([clr+p[0], p[1], z_ceil-1]) cylinder(d=btn_d, h=plate_t+2); } } module lid_for_print() { translate([0,0,z_top]) rotate([180,0,0]) lid(); } /* ---------------- Preview ghost (approximate) ---------------- */ module ghost() { color("seagreen", 0.6) { translate([clr, -pcb_w/2, z_pcb_bot]) cube([pcb_l, pcb_w, pcb_t]); for (s=[-1,1]) translate([clr+header_x0-1.27, s*row_dist/2-1.27, z_pcb_bot-2.5]) cube([(header_pins-1)*pin_pitch+2.54, 2.54, 2.5]); for (s=[-1,1]) for (i=[0:header_pins-1]) translate([clr+header_x0+i*pin_pitch-0.32, s*row_dist/2-0.32, z_pcb_bot-pin_below]) cube([0.64,0.64,pin_below]); } color("silver", 0.8) { for (s=[-1,1]) translate([clr-0.5, s*6.5-4.47, z_pcb_top]) cube([7.3, 8.94, 3.2]); // USB-C (approx. y) translate([clr+pcb_l-25.5, -9, z_pcb_top]) cube([25.5, 18, 3.2]); // module } color("black", 0.8) for (p=[boot_pos, rst_pos]) translate([clr+p[0], p[1], z_pcb_top]) cylinder(d=3, h=3.5); } /* ---------------- Output ---------------- */ if (part == "base") base(); else if (part == "lid") lid_for_print(); else if (part == "assembled") { base(); translate([0,0,explode]) lid(); if (show_board) ghost(); } else { base(); translate([0, -(W_i + 2*wall + 10), 0]) lid_for_print(); }