// Waveshare 2.13" e-Paper HAT (B) — ESP32 Nametag Case // Display slides in from X+; FPC cable exits freely out the open X+ side // --- 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 = 20.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) TOL = 0.3; // 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 = 5; // preview: mm to remove from Y+ (0 = full model) SHOW_DISP = true; SCREEN_EXPLODE = 0; // mm to pull display toward X+ in preview // --- Slip-Fit Cap (X+ end) --- SHOW_CAP = true; // show cap in preview CAP_ENGAGE = 8.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_TIP = 0.8; // minimum wall thickness at X- (open) edge after taper CAP_EXPLODE = 5; // 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 + cav_w; // no X+ wall; open for display entry case_h = WALL + DISP_T + LIP; 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_TOL; // bore Y width _cap_bore_h = case_h + 2 * CAP_TOL; // bore Z height // FPC exit slot spans the full FPC pocket height (pocket floor → cavity ceiling) _cap_slot_w = FPC_W + 1.0; // slot Y width _cap_slot_h = FPC_FLOOR_DROP + DISP_T + TOL + 2 * CAP_TOL; // slot Z height // 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]) cube([cav_w + 0.01, cav_d, DISP_T + TOL]); // Active area window + chamfered top edges translate([win_x, win_y, WALL + 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 - 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 translate([_lip_x, _lip_y, -0.01]) cube([_lip_il, _lip_iw, MAG_LIP_H + 0.01]); } } // ------------------------------------------------------------------- // Slip-fit cap — slides over X+ end of case; FPC exits through end-wall slot // ------------------------------------------------------------------- module cable_cap() { // Translate so case exterior Y=0, Z=0 aligns with bore inside walls translate([case_w - CAP_ENGAGE + CAP_EXPLODE, -CAP_TOL - CAP_WALL, -CAP_TOL - CAP_WALL]) difference() { // Outer shell: hull between two square profiles // X- (open end): inset to CAP_TIP wall thickness // X+ (closed end): full CAP_WALL thickness hull() { translate([0, CAP_WALL - CAP_TIP, CAP_WALL - CAP_TIP]) cube([0.01, _cap_bore_w + 2 * CAP_TIP, _cap_bore_h + 2 * CAP_TIP]); translate([CAP_ENGAGE + CAP_WALL - 0.01, 0, 0]) cube([0.01, _cap_bore_w + 2 * CAP_WALL, _cap_bore_h + 2 * CAP_WALL]); } // Bore — open on X- side; closes at end wall (local x = CAP_ENGAGE) translate([-0.01, CAP_WALL, CAP_WALL]) cube([CAP_ENGAGE + 0.02, _cap_bore_w, _cap_bore_h]); // FPC cable exit slot through end wall // Y: centered on case; Z: from FPC pocket floor to display cavity ceiling translate([CAP_ENGAGE - 0.01, CAP_WALL + CAP_TOL + case_d / 2 - _cap_slot_w / 2, CAP_WALL + CAP_TOL + WALL - FPC_FLOOR_DROP]) cube([CAP_WALL + 0.02, _cap_slot_w, _cap_slot_h]); } } color("#004B49") intersection() { epd_case(); cube([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 ($preview && SHOW_DISP) translate([WALL + TOL + SCREEN_EXPLODE, WALL + TOL, WALL]) EPD213B(); if (SHOW_CAP) color("#1A5C5B") intersection() { cable_cap(); translate([0, -(CAP_TOL + CAP_WALL), -(CAP_TOL + CAP_WALL)]) cube([case_w + CAP_WALL + CAP_ENGAGE + CAP_EXPLODE + 1, ($preview && CLIP_Y > 0) ? max(0.01, case_d + CAP_TOL + CAP_WALL - CLIP_Y) : case_d + 2 * (CAP_TOL + CAP_WALL) + 1, case_h + 2 * (CAP_TOL + CAP_WALL) + 1]); }