// Waveshare 2.13" e-Paper HAT (B) — ESP32 Nametag Case // Display slides in from X+; FPC cable exits freely out the open X+ side $fn = $preview ? 32 : 64; // --- Display Dimensions (landscape: portrait rotated 90°) --- DISP_W = 59.20; // overall width (X) — portrait height DISP_D = 29.20; // overall depth (Y) — portrait width DISP_T = 1.0; // thickness (Z) // --- Active (Visible) Area --- ACT_W = 48.55; // active area width (X) — portrait AA height ACT_D = 23.71; // active area depth (Y) — portrait AA width // Border offsets from display edge to active area: // X-: portrait top border ≈ 1.95 mm (closed/backstop side) // X+: portrait FPC border ≈ 8.70 mm (open/entry side) // Y±: symmetric ≈ 2.75 mm each side ACT_X_OFFSET = 1.95; ACT_Y_OFFSET = 2.75; // --- FPC Cable (exits X+ side) --- FPC_W = 12.5; FPC_T = 0.3; FPC_LEN = 20.0; // visualization length of hanging tail // --- FPC Return Pocket --- FPC_FLOOR_DROP = 0.75; // extra depth below display cavity floor FPC_POCKET_LEN = 30.0; // how far in from X+ face the pocket extends FPC_POCKET_PAD = 2.0; // padding on each side of FPC cable width // --- Case Parameters --- WALL = 3.0; // must exceed MAG_LIP_H (2.0) to keep pocket floor LIP = 1.5; // front bezel lip thickness (Z) Z_BASE = 0.75; // extra Z- wall thickness; shifts cavity/FPC pocket up, leaving magnet pocket and cap recess anchored — tune to fix thin walls TOL = 0.2; // per-side display clearance CHAM = 1.0; CHAM_ANGLE = 60; CORNER_R = 2.5; CLIP_Z = 0; // preview: mm to remove from Z+ (0 = full model) CLIP_Y = 0; // preview: mm to remove from Y+ (0 = full model) SHOW_DISP = true; SCREEN_EXPLODE = 0; // mm to pull display toward X+ in preview // --- Output --- PART = "all"; // "all" = preview scene; "case" = case STL at origin; "cap" = cap STL at origin // --- Slip-Fit Cap (X+ end) --- SHOW_CAP = true; // show cap in preview CAP_RIBS = false; // include retention ribs in cap CAP_DETENT = true; // include snap detent nubs and dimples DETENT_R = 0.5; // detent nub/dimple radius (mm); tune across prints CAP_PAD = 4.0; // Min cap bore depth CAP_ENGAGE = CAP_PAD + 6.0; // sleeve engagement length over case exterior (X) CAP_WALL = 1.5; // sleeve and end-wall thickness CAP_TOL = 0.2; // per-side slip clearance between bore and case exterior CAP_BEVEL = 1.0; // chamfer size on X+ face edges to guide cap onto case CAP_EXPLODE = CAP_PAD + 10; // preview: mm to pull cap away from case in X+ (0 = seated) // --- Magnetic Strip Locating Lip (rear face) --- MAG_L = 1.772 * 25.4; // strip length ≈ 45.01 mm MAG_W = 0.512 * 25.4; // strip width ≈ 13.00 mm MAG_LIP_H = 2.0; // lip height protruding from rear face MAG_LIP_WALL = 1.0; // lip wall thickness MAG_TOL = 0.2; // per-side clearance for strip fit // --- Derived Dimensions --- cav_w = DISP_W + TOL * 2; cav_d = DISP_D + TOL * 2; case_w = WALL + DISP_W + (TOL * 1.5); // ends at display X+ face; cap retains display case_h = WALL + DISP_T + LIP + Z_BASE; case_d = WALL * 2 + cav_d; _cham_xy = CHAM * tan(CHAM_ANGLE); win_x = WALL + TOL + ACT_X_OFFSET; win_y = WALL + TOL + ACT_Y_OFFSET; // FPC return pocket geometry (X+ face) fpc_pkt_y = WALL + TOL + (DISP_D - FPC_W) / 2 - FPC_POCKET_PAD; fpc_pkt_yw = FPC_W + 2 * FPC_POCKET_PAD; // Cap derived _cap_bore_w = case_d - 2*CAP_WALL; // bore Y width (fits recessed case) _cap_bore_h = case_h + CAP_TOL; // bore Z height (CAP_TOL clearance on each Z face) _cap_gap_w = (case_w - CAP_ENGAGE + CAP_PAD) - (win_x + ACT_W + _cham_xy); // gap between cap X- face and beveled bezel X+ end _detent_x = (CAP_ENGAGE - CAP_PAD) / 2; // detent offset from cap X- face; same in cap local and case global offset _detent_z = (CAP_WALL + CAP_TOL + case_h) / 2; // Z center of recessed engagement face // Magnetic strip recess geometry (centered on back face) _lip_il = MAG_L + 2 * MAG_TOL; _lip_iw = MAG_W + 2 * MAG_TOL; _lip_x = case_w / 2 - _lip_il / 2; _lip_y = case_d / 2 - _lip_iw / 2; // ------------------------------------------------------------------- // Display mockup — approximate geometry for preview only // ------------------------------------------------------------------- module EPD213B() { color([0.82, 0.84, 0.80]) cube([DISP_W, DISP_D, DISP_T]); color("white") translate([ACT_X_OFFSET, ACT_Y_OFFSET, DISP_T]) cube([ACT_W, ACT_D, 0.02]); color([1.0, 0.85, 0.1]) translate([DISP_W, (DISP_D - FPC_W) / 2, (DISP_T - FPC_T) / 2]) cube([FPC_LEN, FPC_W, FPC_T]); } // ------------------------------------------------------------------- module rounded_box(l, w, h, r) { hull() for (dx = [r, l-r], dy = [r, w-r]) translate([dx, dy, 0]) cylinder(r=r, h=h, $fn=32); } module epd_case() { difference() { rounded_box(case_w, case_d, case_h, CORNER_R); // Display cavity — extends 0.01 mm past X+ face to leave that side open translate([WALL, WALL, WALL + Z_BASE]) cube([cav_w + 0.01, cav_d, DISP_T + TOL]); // Active area window + chamfered top edges translate([win_x, win_y, WALL + Z_BASE + DISP_T - 0.01]) cube([ACT_W, ACT_D, LIP - CHAM + 0.02]); hull() { translate([win_x, win_y, case_h - CHAM]) cube([ACT_W, ACT_D, 0.01]); translate([win_x - _cham_xy, win_y - _cham_xy, case_h]) cube([ACT_W + 2*_cham_xy, ACT_D + 2*_cham_xy, 0.01]); } // FPC return pocket — drops floor of display cavity near X+ face translate([case_w - FPC_POCKET_LEN, fpc_pkt_y, WALL + Z_BASE - FPC_FLOOR_DROP - 0.01]) cube([FPC_POCKET_LEN + 0.02, fpc_pkt_yw, DISP_T + TOL + FPC_FLOOR_DROP + 0.01]); // Magnetic strip recess in back face // TODO: wall between Z+ of this pocket and Z- of FPC return pocket is ~1.0 mm at Z_BASE=0.75 // (MAG_LIP_H=2.0 vs WALL+Z_BASE-FPC_FLOOR_DROP=3.0) — tune Z_BASE after test prints translate([_lip_x, _lip_y, -0.01]) cube([_lip_il, _lip_iw, MAG_LIP_H + 0.01]); // Cap wall recess — step in z- face at X+ end so cap sits flush // TODO: wall between Z- of FPC return pocket and this recessed Z- face is ~1.3 mm at Z_BASE=0.75 // (WALL+Z_BASE-FPC_FLOOR_DROP=3.0 vs CAP_WALL+CAP_TOL=1.7) — tune Z_BASE after test prints translate([case_w - CAP_ENGAGE + CAP_PAD, -0.01, -0.01]) cube([CAP_ENGAGE + 0.02 - CAP_PAD, case_d + 0.02, CAP_WALL + CAP_TOL + 0.01]); // Detent dimples on Y± faces — accept cap nubs when fully seated if (CAP_DETENT) for (dy = [CAP_WALL + CAP_TOL, case_d - CAP_WALL - CAP_TOL]) translate([case_w - CAP_ENGAGE + CAP_PAD + _detent_x, dy, _detent_z]) sphere(r=DETENT_R); // Cap wall recess — step in y- face at X+ end so cap sits flush translate([case_w - CAP_ENGAGE + CAP_PAD, -0.01, -0.01]) cube([CAP_ENGAGE - CAP_PAD + 0.02, CAP_WALL + CAP_TOL + 0.01, case_h + 0.02]); // Cap wall recess — step in y+ face at X+ end so cap sits flush translate([case_w - CAP_ENGAGE + CAP_PAD, case_d - CAP_WALL - CAP_TOL, -0.01]) cube([CAP_ENGAGE - CAP_PAD + 0.02, CAP_WALL + CAP_TOL + 0.01, case_h + 0.02]); // Bevel all 4 exterior edges of X+ face — guides cap onto case during installation // Y± faces are recessed by (CAP_WALL + CAP_TOL); Z- face is recessed by CAP_WALL translate([0, 0, -0.01]) linear_extrude(case_h + 0.02) polygon([[case_w - CAP_BEVEL, CAP_WALL + CAP_TOL - 0.01], [case_w + 0.01, CAP_WALL + CAP_TOL - 0.01], [case_w + 0.01, CAP_WALL + CAP_TOL + CAP_BEVEL]]); translate([0, 0, -0.01]) linear_extrude(case_h + 0.02) polygon([[case_w - CAP_BEVEL, case_d - CAP_WALL - CAP_TOL + 0.01], [case_w + 0.01, case_d - CAP_WALL - CAP_TOL + 0.01], [case_w + 0.01, case_d - CAP_WALL - CAP_TOL - CAP_BEVEL]]); translate([0, case_d + 0.01, 0]) rotate([90, 0, 0]) linear_extrude(case_d + 0.02) polygon([[case_w - CAP_BEVEL, CAP_WALL - 0.01], [case_w + 0.01, CAP_WALL - 0.01], [case_w + 0.01, CAP_WALL + CAP_BEVEL]]); translate([0, case_d + 0.01, 0]) rotate([90, 0, 0]) linear_extrude(case_d + 0.02) polygon([[case_w - CAP_BEVEL, case_h + 0.01], [case_w + 0.01, case_h + 0.01], [case_w + 0.01, case_h - CAP_BEVEL]]); } } // ------------------------------------------------------------------- // Slip-fit cap — slides over X+ end of case; FPC exits through end-wall slot // ------------------------------------------------------------------- module cable_cap() { _bore_h = _cap_bore_h - CAP_WALL; _bore_r = _bore_h / 2; _cap_h = _cap_bore_h + CAP_WALL; _cap_cy = (_cap_bore_w + 2 * CAP_WALL) / 2; // Y center of cap union() { difference() { // Outer shell: flat rectangle at open end, rounded rectangle at closed end hull() { cube([0.01, _cap_bore_w + 2 * CAP_WALL, _cap_bore_h + CAP_WALL]); for (dy = [CORNER_R, _cap_bore_w + 2*CAP_WALL - CORNER_R]) translate([CAP_ENGAGE + CAP_WALL - CORNER_R, dy, 0]) cylinder(r=CORNER_R, h=_cap_bore_h + CAP_WALL); } // Rounded bore translate([-0.01, CAP_WALL, CAP_WALL]) cube([CAP_ENGAGE - _bore_r + 0.01, _cap_bore_w, _bore_h]); translate([CAP_ENGAGE - _bore_r, _cap_bore_w, _cap_bore_h - _bore_r]) rotate([90,0,0]) cylinder(h=_cap_bore_w - CAP_WALL, r=_bore_r); } // Retention ribs — flush with X- face, full cap height, 2×2 mm, just outside FPC pocket if (CAP_RIBS) for (rib_dy = [fpc_pkt_y - CAP_WALL, fpc_pkt_y + fpc_pkt_yw]) translate([CAP_ENGAGE - CAP_PAD, rib_dy, 0]) cube([CAP_WALL, CAP_WALL, _cap_h]); // Detent nubs on Y± bore walls — snap into case dimples when fully seated if (CAP_DETENT) for (ny = [CAP_WALL, case_d - CAP_WALL]) translate([_detent_x, ny, _detent_z]) sphere(r=DETENT_R); // Chamfer tab: extends window bezel chamfer across the gap to the cap X- face hull() { translate([-_cap_gap_w, 0, _cap_bore_h]) cube([_cap_gap_w, case_d, 0.01]); translate([-0.01, 0, _cap_bore_h + CAP_WALL - 0.01]) cube([0.01, case_d, 0.01]); } } } _clip = [case_w + 1, ($preview && CLIP_Y > 0) ? max(0.01, case_d - CLIP_Y) : case_d + 1, ($preview && CLIP_Z > 0) ? max(0.01, case_h - CLIP_Z) : case_h + 1]; if (PART == "case") { epd_case(); } else if (PART == "cap") { cable_cap(); } else { color("#004B49") intersection() { epd_case(); cube(_clip); } if ($preview && SHOW_DISP) translate([WALL + TOL + SCREEN_EXPLODE, WALL + TOL, WALL + Z_BASE]) EPD213B(); if (SHOW_CAP) color("#1A3A6B") translate([case_w - CAP_ENGAGE + CAP_EXPLODE, 0, 0]) intersection() { cable_cap(); translate([-_cap_gap_w, 0, -(CAP_TOL + CAP_WALL)]) cube([CAP_ENGAGE + CAP_WALL + 1 + _cap_gap_w, _clip[1], case_h + 2*(CAP_TOL + CAP_WALL) + 1]); } }