cable_cap.scad 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. // Waveshare 2.13" e-Paper HAT (B) — ESP32 Nametag Case
  2. // Display slides in from X+; FPC cable exits freely out the open X+ side
  3. $fn = $preview ? 32 : 64;
  4. // --- Display Dimensions (landscape: portrait rotated 90°) ---
  5. DISP_W = 59.20; // overall width (X) — portrait height
  6. DISP_D = 29.20; // overall depth (Y) — portrait width
  7. DISP_T = 1.0; // thickness (Z)
  8. // --- Active (Visible) Area ---
  9. ACT_W = 48.55; // active area width (X) — portrait AA height
  10. ACT_D = 23.71; // active area depth (Y) — portrait AA width
  11. // Border offsets from display edge to active area:
  12. // X-: portrait top border ≈ 1.95 mm (closed/backstop side)
  13. // X+: portrait FPC border ≈ 8.70 mm (open/entry side)
  14. // Y±: symmetric ≈ 2.75 mm each side
  15. ACT_X_OFFSET = 1.95;
  16. ACT_Y_OFFSET = 2.75;
  17. // --- FPC Cable (exits X+ side) ---
  18. FPC_W = 12.5;
  19. FPC_T = 0.3;
  20. FPC_LEN = 20.0; // visualization length of hanging tail
  21. // --- FPC Return Pocket ---
  22. FPC_FLOOR_DROP = 0.75; // extra depth below display cavity floor
  23. FPC_POCKET_LEN = 30.0; // how far in from X+ face the pocket extends
  24. FPC_POCKET_PAD = 2.0; // padding on each side of FPC cable width
  25. // --- Case Parameters ---
  26. WALL = 3.0; // must exceed MAG_LIP_H (2.0) to keep pocket floor
  27. LIP = 1.5; // front bezel lip thickness (Z)
  28. 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
  29. TOL = 0.2; // per-side display clearance
  30. CHAM = 1.0;
  31. CHAM_ANGLE = 60;
  32. CORNER_R = 2.5;
  33. CLIP_Z = 0; // preview: mm to remove from Z+ (0 = full model)
  34. CLIP_Y = 0; // preview: mm to remove from Y+ (0 = full model)
  35. SHOW_DISP = true;
  36. SCREEN_EXPLODE = 0; // mm to pull display toward X+ in preview
  37. // --- Output ---
  38. PART = "all"; // "all" = preview scene; "case" = case STL at origin; "cap" = cap STL at origin
  39. // --- Slip-Fit Cap (X+ end) ---
  40. SHOW_CAP = true; // show cap in preview
  41. CAP_RIBS = false; // include retention ribs in cap
  42. CAP_DETENT = true; // include snap detent nubs and dimples
  43. DETENT_R = 0.5; // detent nub/dimple radius (mm); tune across prints
  44. CAP_PAD = 4.0; // Min cap bore depth
  45. CAP_ENGAGE = CAP_PAD + 6.0; // sleeve engagement length over case exterior (X)
  46. CAP_WALL = 1.5; // sleeve and end-wall thickness
  47. CAP_TOL = 0.2; // per-side slip clearance between bore and case exterior
  48. CAP_BEVEL = 1.0; // chamfer size on X+ face edges to guide cap onto case
  49. CAP_EXPLODE = CAP_PAD + 10; // preview: mm to pull cap away from case in X+ (0 = seated)
  50. // --- Magnetic Strip Locating Lip (rear face) ---
  51. MAG_L = 1.772 * 25.4; // strip length ≈ 45.01 mm
  52. MAG_W = 0.512 * 25.4; // strip width ≈ 13.00 mm
  53. MAG_LIP_H = 2.0; // lip height protruding from rear face
  54. MAG_LIP_WALL = 1.0; // lip wall thickness
  55. MAG_TOL = 0.2; // per-side clearance for strip fit
  56. // --- Derived Dimensions ---
  57. cav_w = DISP_W + TOL * 2;
  58. cav_d = DISP_D + TOL * 2;
  59. case_w = WALL + DISP_W + (TOL * 1.5); // ends at display X+ face; cap retains display
  60. case_h = WALL + DISP_T + LIP + Z_BASE;
  61. case_d = WALL * 2 + cav_d;
  62. _cham_xy = CHAM * tan(CHAM_ANGLE);
  63. win_x = WALL + TOL + ACT_X_OFFSET;
  64. win_y = WALL + TOL + ACT_Y_OFFSET;
  65. // FPC return pocket geometry (X+ face)
  66. fpc_pkt_y = WALL + TOL + (DISP_D - FPC_W) / 2 - FPC_POCKET_PAD;
  67. fpc_pkt_yw = FPC_W + 2 * FPC_POCKET_PAD;
  68. // Cap derived
  69. _cap_bore_w = case_d - 2*CAP_WALL; // bore Y width (fits recessed case)
  70. _cap_bore_h = case_h + CAP_TOL; // bore Z height (CAP_TOL clearance on each Z face)
  71. _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
  72. _detent_x = (CAP_ENGAGE - CAP_PAD) / 2; // detent offset from cap X- face; same in cap local and case global offset
  73. _detent_z = (CAP_WALL + CAP_TOL + case_h) / 2; // Z center of recessed engagement face
  74. // Magnetic strip recess geometry (centered on back face)
  75. _lip_il = MAG_L + 2 * MAG_TOL;
  76. _lip_iw = MAG_W + 2 * MAG_TOL;
  77. _lip_x = case_w / 2 - _lip_il / 2;
  78. _lip_y = case_d / 2 - _lip_iw / 2;
  79. // -------------------------------------------------------------------
  80. // Display mockup — approximate geometry for preview only
  81. // -------------------------------------------------------------------
  82. module EPD213B() {
  83. color([0.82, 0.84, 0.80])
  84. cube([DISP_W, DISP_D, DISP_T]);
  85. color("white")
  86. translate([ACT_X_OFFSET, ACT_Y_OFFSET, DISP_T])
  87. cube([ACT_W, ACT_D, 0.02]);
  88. color([1.0, 0.85, 0.1])
  89. translate([DISP_W, (DISP_D - FPC_W) / 2, (DISP_T - FPC_T) / 2])
  90. cube([FPC_LEN, FPC_W, FPC_T]);
  91. }
  92. // -------------------------------------------------------------------
  93. module rounded_box(l, w, h, r) {
  94. hull()
  95. for (dx = [r, l-r], dy = [r, w-r])
  96. translate([dx, dy, 0]) cylinder(r=r, h=h, $fn=32);
  97. }
  98. module epd_case() {
  99. difference() {
  100. rounded_box(case_w, case_d, case_h, CORNER_R);
  101. // Display cavity — extends 0.01 mm past X+ face to leave that side open
  102. translate([WALL, WALL, WALL + Z_BASE])
  103. cube([cav_w + 0.01, cav_d, DISP_T + TOL]);
  104. // Active area window + chamfered top edges
  105. translate([win_x, win_y, WALL + Z_BASE + DISP_T - 0.01])
  106. cube([ACT_W, ACT_D, LIP - CHAM + 0.02]);
  107. hull() {
  108. translate([win_x, win_y, case_h - CHAM])
  109. cube([ACT_W, ACT_D, 0.01]);
  110. translate([win_x - _cham_xy, win_y - _cham_xy, case_h])
  111. cube([ACT_W + 2*_cham_xy, ACT_D + 2*_cham_xy, 0.01]);
  112. }
  113. // FPC return pocket — drops floor of display cavity near X+ face
  114. translate([case_w - FPC_POCKET_LEN, fpc_pkt_y, WALL + Z_BASE - FPC_FLOOR_DROP - 0.01])
  115. cube([FPC_POCKET_LEN + 0.02, fpc_pkt_yw, DISP_T + TOL + FPC_FLOOR_DROP + 0.01]);
  116. // Magnetic strip recess in back face
  117. // TODO: wall between Z+ of this pocket and Z- of FPC return pocket is ~1.0 mm at Z_BASE=0.75
  118. // (MAG_LIP_H=2.0 vs WALL+Z_BASE-FPC_FLOOR_DROP=3.0) — tune Z_BASE after test prints
  119. translate([_lip_x, _lip_y, -0.01])
  120. cube([_lip_il, _lip_iw, MAG_LIP_H + 0.01]);
  121. // Cap wall recess — step in z- face at X+ end so cap sits flush
  122. // TODO: wall between Z- of FPC return pocket and this recessed Z- face is ~1.3 mm at Z_BASE=0.75
  123. // (WALL+Z_BASE-FPC_FLOOR_DROP=3.0 vs CAP_WALL+CAP_TOL=1.7) — tune Z_BASE after test prints
  124. translate([case_w - CAP_ENGAGE + CAP_PAD, -0.01, -0.01])
  125. cube([CAP_ENGAGE + 0.02 - CAP_PAD, case_d + 0.02, CAP_WALL + CAP_TOL + 0.01]);
  126. // Detent dimples on Y± faces — accept cap nubs when fully seated
  127. if (CAP_DETENT)
  128. for (dy = [CAP_WALL + CAP_TOL, case_d - CAP_WALL - CAP_TOL])
  129. translate([case_w - CAP_ENGAGE + CAP_PAD + _detent_x, dy, _detent_z])
  130. sphere(r=DETENT_R);
  131. // Cap wall recess — step in y- face at X+ end so cap sits flush
  132. translate([case_w - CAP_ENGAGE + CAP_PAD, -0.01, -0.01])
  133. cube([CAP_ENGAGE - CAP_PAD + 0.02, CAP_WALL + CAP_TOL + 0.01, case_h + 0.02]);
  134. // Cap wall recess — step in y+ face at X+ end so cap sits flush
  135. translate([case_w - CAP_ENGAGE + CAP_PAD, case_d - CAP_WALL - CAP_TOL, -0.01])
  136. cube([CAP_ENGAGE - CAP_PAD + 0.02, CAP_WALL + CAP_TOL + 0.01, case_h + 0.02]);
  137. // Bevel all 4 exterior edges of X+ face — guides cap onto case during installation
  138. // Y± faces are recessed by (CAP_WALL + CAP_TOL); Z- face is recessed by CAP_WALL
  139. translate([0, 0, -0.01])
  140. linear_extrude(case_h + 0.02)
  141. polygon([[case_w - CAP_BEVEL, CAP_WALL + CAP_TOL - 0.01],
  142. [case_w + 0.01, CAP_WALL + CAP_TOL - 0.01],
  143. [case_w + 0.01, CAP_WALL + CAP_TOL + CAP_BEVEL]]);
  144. translate([0, 0, -0.01])
  145. linear_extrude(case_h + 0.02)
  146. polygon([[case_w - CAP_BEVEL, case_d - CAP_WALL - CAP_TOL + 0.01],
  147. [case_w + 0.01, case_d - CAP_WALL - CAP_TOL + 0.01],
  148. [case_w + 0.01, case_d - CAP_WALL - CAP_TOL - CAP_BEVEL]]);
  149. translate([0, case_d + 0.01, 0])
  150. rotate([90, 0, 0])
  151. linear_extrude(case_d + 0.02)
  152. polygon([[case_w - CAP_BEVEL, CAP_WALL - 0.01],
  153. [case_w + 0.01, CAP_WALL - 0.01],
  154. [case_w + 0.01, CAP_WALL + CAP_BEVEL]]);
  155. translate([0, case_d + 0.01, 0])
  156. rotate([90, 0, 0])
  157. linear_extrude(case_d + 0.02)
  158. polygon([[case_w - CAP_BEVEL, case_h + 0.01],
  159. [case_w + 0.01, case_h + 0.01],
  160. [case_w + 0.01, case_h - CAP_BEVEL]]);
  161. }
  162. }
  163. // -------------------------------------------------------------------
  164. // Slip-fit cap — slides over X+ end of case; FPC exits through end-wall slot
  165. // -------------------------------------------------------------------
  166. module cable_cap() {
  167. _bore_h = _cap_bore_h - CAP_WALL;
  168. _bore_r = _bore_h / 2;
  169. _cap_h = _cap_bore_h + CAP_WALL;
  170. _cap_cy = (_cap_bore_w + 2 * CAP_WALL) / 2; // Y center of cap
  171. union() {
  172. difference() {
  173. // Outer shell: flat rectangle at open end, rounded rectangle at closed end
  174. hull() {
  175. cube([0.01, _cap_bore_w + 2 * CAP_WALL, _cap_bore_h + CAP_WALL]);
  176. for (dy = [CORNER_R, _cap_bore_w + 2*CAP_WALL - CORNER_R])
  177. translate([CAP_ENGAGE + CAP_WALL - CORNER_R, dy, 0])
  178. cylinder(r=CORNER_R, h=_cap_bore_h + CAP_WALL);
  179. }
  180. // Rounded bore
  181. translate([-0.01, CAP_WALL, CAP_WALL])
  182. cube([CAP_ENGAGE - _bore_r + 0.01, _cap_bore_w, _bore_h]);
  183. translate([CAP_ENGAGE - _bore_r, _cap_bore_w, _cap_bore_h - _bore_r])
  184. rotate([90,0,0])
  185. cylinder(h=_cap_bore_w - CAP_WALL, r=_bore_r);
  186. }
  187. // Retention ribs — flush with X- face, full cap height, 2×2 mm, just outside FPC pocket
  188. if (CAP_RIBS)
  189. for (rib_dy = [fpc_pkt_y - CAP_WALL, fpc_pkt_y + fpc_pkt_yw])
  190. translate([CAP_ENGAGE - CAP_PAD, rib_dy, 0])
  191. cube([CAP_WALL, CAP_WALL, _cap_h]);
  192. // Detent nubs on Y± bore walls — snap into case dimples when fully seated
  193. if (CAP_DETENT)
  194. for (ny = [CAP_WALL, case_d - CAP_WALL])
  195. translate([_detent_x, ny, _detent_z])
  196. sphere(r=DETENT_R);
  197. // Chamfer tab: extends window bezel chamfer across the gap to the cap X- face
  198. hull() {
  199. translate([-_cap_gap_w, 0, _cap_bore_h])
  200. cube([_cap_gap_w, case_d, 0.01]);
  201. translate([-0.01, 0, _cap_bore_h + CAP_WALL - 0.01])
  202. cube([0.01, case_d, 0.01]);
  203. }
  204. }
  205. }
  206. _clip = [case_w + 1,
  207. ($preview && CLIP_Y > 0) ? max(0.01, case_d - CLIP_Y) : case_d + 1,
  208. ($preview && CLIP_Z > 0) ? max(0.01, case_h - CLIP_Z) : case_h + 1];
  209. if (PART == "case") {
  210. epd_case();
  211. } else if (PART == "cap") {
  212. cable_cap();
  213. } else {
  214. color("#004B49")
  215. intersection() { epd_case(); cube(_clip); }
  216. if ($preview && SHOW_DISP)
  217. translate([WALL + TOL + SCREEN_EXPLODE, WALL + TOL, WALL + Z_BASE])
  218. EPD213B();
  219. if (SHOW_CAP)
  220. color("#1A3A6B")
  221. translate([case_w - CAP_ENGAGE + CAP_EXPLODE, 0, 0])
  222. intersection() {
  223. cable_cap();
  224. translate([-_cap_gap_w, 0, -(CAP_TOL + CAP_WALL)])
  225. cube([CAP_ENGAGE + CAP_WALL + 1 + _cap_gap_w,
  226. _clip[1],
  227. case_h + 2*(CAP_TOL + CAP_WALL) + 1]);
  228. }
  229. }