Pip-Boy inspired phone holder built around the PINE64 PinePhone
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

349 lines
10 KiB

  1. // dependency: https://github.com/revarbat/BOSL
  2. include <BOSL/constants.scad>
  3. use <BOSL/transforms.scad>
  4. use <BOSL/shapes.scad>
  5. use <BOSL/masks.scad>
  6. use <BOSL/sliders.scad>
  7. // DEBUG:
  8. //validation = 1;
  9. XRAY_POSITIVE=1;
  10. XRAY_BACKPLANE=2;
  11. //xray = 2;
  12. // EXPORT:
  13. MODEL_HARNESS_LEFT=1;
  14. MODEL_HARNESS_RIGHT=2;
  15. MODEL_HARNESS_BOTTOM_PIP=3;
  16. MODEL_HARNESS_TOP=4;
  17. MODEL_HARNESS_SLIDER_FRONT=5;
  18. MODEL_HARNESS_SLIDER_BACK=6;
  19. export=3; // [0:6]
  20. function is_not_export() = is_undef(export) || export == 0;
  21. function is_export() = !is_not_export();
  22. function is_model_strict(m) = is_export() && export == m;
  23. function is_model(m) = is_not_export() || export == m;
  24. function get_phone_size() = [160, 76.7, 10];
  25. backplane_pip_slideout=10;
  26. module cubi(s, fillet=0, edges=EDGES_ALL) {
  27. if ($preview) {
  28. cube(s, center=true);
  29. } else {
  30. cuboid(s, fillet=fillet, edges=edges);
  31. }
  32. }
  33. module phone() {
  34. s=get_phone_size();
  35. color("gray")
  36. cubi(s, fillet=5);
  37. }
  38. module arm() {
  39. $fn=6;
  40. s=[210, 55, 50];
  41. color("SaddleBrown")
  42. down(s.z/2 + get_phone_size().z/2) cubi(s, fillet=20);
  43. }
  44. module phone_harness() {
  45. ops=get_phone_size();
  46. ps=[ops.x+0.2, ops.y+0.2, ops.z+0.2];
  47. shell_wall=3;
  48. shell_rim_wall=3;
  49. rim_edge_thickness=1.2;
  50. rim_edge_tolerance=0.4;
  51. expose_back_camera=false;
  52. screwmount_screw=3;
  53. harness_divider=[16, 0, 1.3];
  54. harness_size=[ps.x+shell_wall*2, ps.y+shell_wall*2, ps.z+shell_wall*2];
  55. rim_size=[harness_size.x+2*shell_rim_wall, harness_size.y+2*shell_rim_wall, 6];
  56. w=shell_wall;
  57. f=2;
  58. rf=5;
  59. hs=harness_size;
  60. backplane_rim_size=[30, ps.y*0.95, shell_wall/2];
  61. // ==== SHELL ====
  62. module shell() {
  63. $fn = $preview ? 6 : 30;
  64. difference() {
  65. cubi(hs, fillet=f);
  66. cubi(ps, fillet=f);
  67. }
  68. shell_rim();
  69. shell_screwmounts();
  70. }
  71. module shell_rim() {
  72. $fn = $preview ? 6 : 30;
  73. w=shell_rim_wall;
  74. s=rim_size;
  75. intersection() {
  76. difference() {
  77. cubi(s, fillet=rf, edges=EDGES_Z_ALL);
  78. cubi(ps, fillet=f, edges=EDGES_Z_ALL);
  79. }
  80. cubi(s, fillet=1);
  81. }
  82. rail_extension=10;
  83. fwd(hs.y/2) xrot(90) harness_rail(slider_len_front+rail_extension);
  84. back(hs.y/2) xrot(-90) harness_rail(slider_len_back+rail_extension);
  85. }
  86. module shell_rim_edge(thickness, groove, tolerance=rim_edge_tolerance) {
  87. $fn = $preview ? 6 : 30;
  88. w=thickness + (groove ? tolerance : 0);
  89. offset=(groove ? 0 : tolerance/2);
  90. res=[hs.x+2*(w+offset), hs.y+2*(w+offset), 3];
  91. resi=[hs.x+2*offset, hs.y+2*offset, res.z+1];
  92. up(0.0) difference() {
  93. intersection() {
  94. cubi(res, fillet=rf, edges=EDGES_Z_ALL);
  95. cubi(res, fillet=(groove?1:1.5), edges=EDGES_BOTTOM);
  96. }
  97. cubi(resi, fillet=rf, edges=EDGES_Z_ALL);
  98. }
  99. }
  100. module foreach_screwmount() {
  101. for(x = [0:1]) for (y = [0:1]) {
  102. translate([(-0.5+x)*ps.x, (-0.5+y)*ps.y, -hs.z/2])
  103. children();
  104. }
  105. }
  106. module shell_screwmounts() {
  107. $fn = $preview ? 6 : 30;
  108. r=3;
  109. foreach_screwmount() {
  110. cylinder(r=r, h=hs.z);
  111. }
  112. }
  113. module shell_screwmounts_holes() {
  114. $fn = $preview ? 6 : 30;
  115. foreach_screwmount() {
  116. up(w) cylinder(d2=screwmount_screw, d1=screwmount_screw*0.5, h=hs.z+1);
  117. up(hs.z-1) cylinder(d=screwmount_screw+1.5, h=5);
  118. }
  119. }
  120. module shell_backplane(groove) {
  121. down(hs.z/2 - shell_wall/6) up(backplane_rim_size.z/2) right(harness_divider.x) {
  122. back(backplane_rim_size.y/2) {
  123. teeth=4;
  124. segment=backplane_rim_size.y/teeth;
  125. tooth_size=[backplane_rim_size.x, segment/2, backplane_rim_size.z];
  126. for (i = [0:teeth-1]) {
  127. fwd(i*segment) fwd(tooth_size.y) scale(groove ? 1.02 : 1) cubi(tooth_size, fillet=2, edges=EDGES_Z_LF);
  128. }
  129. }
  130. left(backplane_pip_slideout + 11) if (is_model_strict(MODEL_HARNESS_BOTTOM_PIP)) {
  131. joiner_size=[7 + (groove?backplane_pip_slideout:0), backplane_rim_size.y, backplane_rim_size.z];
  132. scale(groove ? 1.02 : 1) left(-joiner_size.x/2) cubi(joiner_size, fillet=2, edges=EDGES_Z_LF);
  133. }
  134. }
  135. }
  136. // ==== SHELL SLIDER AND RAIL ====
  137. rail_angle=36;
  138. rail_wall=1;
  139. slider_slop=0.1;
  140. slider_len_back=hs.x*0.3;
  141. slider_len_front=hs.x*0.3;
  142. module harness_slider(l) {
  143. base=2;
  144. ss=[l, shell_rim_wall, rim_size.z+rail_wall*2];
  145. up(ss.y + base) xrot(180) slider(l=ss.x, w=ss.z, h=ss.y, base=base, chamfer=0.9, wall=2, ang=rail_angle, slop=slider_slop, orient=ORIENT_X, align=V_UP);
  146. }
  147. module harness_rail(l) {
  148. sw=2;
  149. ss=[l, shell_rim_wall, rim_size.z+rail_wall*2];
  150. rail(l=ss.x, w=ss.z, h=ss.y, chamfer=0.5, ang=rail_angle, orient=ORIENT_X, align=V_UP);
  151. }
  152. // ==== SHELL CUTOUTS ====
  153. module shell_cutouts() {
  154. top_cutout();
  155. bottom_cutout();
  156. left_cutout();
  157. right_cutout();
  158. front_cutout();
  159. shell_screwmounts_holes();
  160. }
  161. // -- TOP --
  162. module screen_cutout() {
  163. s=[138, 69, 100];
  164. up(s.z/2) cubi(s, fillet=3, edges=EDGES_Z_ALL);
  165. }
  166. top_offset=6.5;
  167. module top_speaker_cutout() {
  168. s=[3, 12, 100];
  169. right(ps.x/2 - top_offset) up(s.z/2 - ps.z/3) cubi(s, fillet=1);
  170. }
  171. module front_camera_cutout() {
  172. $fn = $preview ? 6 : 30;
  173. d=5;
  174. s=[d, d, 100];
  175. right(ps.x/2 - top_offset) fwd(11.5) cylinder(d=s.x, h=s.z);
  176. }
  177. module leds_cutout() {
  178. s=[4, 14, 100];
  179. right(ps.x/2 - top_offset) up(s.z/2 - ps.z/3) back(23) cubi(s, fillet=1);
  180. }
  181. module top_cutout() {
  182. screen_cutout();
  183. top_speaker_cutout();
  184. front_camera_cutout();
  185. leds_cutout();
  186. }
  187. // -- BOTTOM --
  188. module back_camera_cutout() {
  189. s=[13, 25, expose_back_camera ? 100 : 11];
  190. right(ps.x/2 - 7.5) fwd(ps.y/2 - 23) down(s.z/2 - ps.z/3) cubi(s, fillet=6, edges=EDGES_Z_ALL);
  191. }
  192. module bottom_speaker_cutout() {
  193. s=[8, 49, 100];
  194. left(ps.x/2 - 13)
  195. difference() {
  196. down(s.z/2 - ps.z/3) cubi(s, fillet=1);
  197. bars=8;
  198. down(ps.z/2)
  199. for (i = [0:bars-1]) {
  200. bs=[s.x+2, 1, 2];
  201. step=s.y/bars;
  202. fwd(i*step - s.y/2 + step/2) down(w) cube(bs, center=true);
  203. }
  204. }
  205. }
  206. module bottom_cutout() {
  207. back_camera_cutout();
  208. bottom_speaker_cutout();
  209. }
  210. // -- RIGHT --
  211. module headphones_cutout() {
  212. $fn = $preview ? 10 : 30;
  213. d1=8.8;
  214. d2=14;
  215. p=[ps.x/2, ps.y/2-24, -0.88];
  216. h=30;
  217. translate(p) yrot(90) down(10) cylinder(h=h, d1=d1, d2=d2);
  218. }
  219. module right_cutout() {
  220. headphones_cutout();
  221. }
  222. // -- LEFT --
  223. module usbc_cutout() {
  224. s=[20, 20, 7];
  225. left(ps.x/2) down(2) cubi(s, fillet=1);
  226. }
  227. module left_cutout() {
  228. usbc_cutout();
  229. }
  230. // -- FRONT --
  231. module buttons_cutout() {
  232. s=[42, 100, 5];
  233. fwd(ps.y/2) right(ps.x/2 - 44.5) cubi(s, fillet=1);
  234. }
  235. module front_cutout() {
  236. buttons_cutout();
  237. }
  238. // ==== MODEL ====
  239. divider=harness_divider;
  240. module model_harness_left() {
  241. intersection() {
  242. intersection() {
  243. translate(divider) downcube([1000, 1000, 1000]);
  244. translate(divider) leftcube([1000, 1000, 1000]);
  245. }
  246. difference() {
  247. shell();
  248. shell_rim_edge(rim_edge_thickness, groove=true, tolerance=rim_edge_tolerance);
  249. shell_cutouts();
  250. shell_backplane(groove=true);
  251. }
  252. }
  253. }
  254. module model_harness_right() {
  255. intersection() {
  256. intersection() {
  257. translate(divider) downcube([1000, 1000, 1000]);
  258. translate(divider) rightcube([1000, 1000, 1000]);
  259. }
  260. difference() {
  261. shell();
  262. shell_rim_edge(rim_edge_thickness, groove=true, tolerance=rim_edge_tolerance);
  263. shell_cutouts();
  264. }
  265. }
  266. shell_backplane(groove=false);
  267. }
  268. module model_harness_top() {
  269. intersection() {
  270. translate(divider) upcube([1000, 1000, 1000]);
  271. difference() {
  272. shell();
  273. shell_cutouts();
  274. }
  275. }
  276. difference() {
  277. shell_rim_edge(rim_edge_thickness, groove=false);
  278. shell_cutouts();
  279. }
  280. }
  281. module model_harness_slider_front() {
  282. difference() {
  283. fwd(hs.y/2) xrot(90) harness_slider(slider_len_front);
  284. front_cutout();
  285. }
  286. }
  287. module model_harness_slider_back() {
  288. back(hs.y/2) xrot(-90) harness_slider(slider_len_back);
  289. }
  290. if (is_model(MODEL_HARNESS_LEFT)) model_harness_left();
  291. if (is_model(MODEL_HARNESS_RIGHT)) model_harness_right();
  292. if (is_model_strict(MODEL_HARNESS_BOTTOM_PIP)) {
  293. model_harness_left();
  294. right(backplane_pip_slideout) model_harness_right();
  295. }
  296. if (is_model(MODEL_HARNESS_TOP)) model_harness_top();
  297. if (is_model(MODEL_HARNESS_SLIDER_FRONT)) model_harness_slider_front();
  298. if (is_model(MODEL_HARNESS_SLIDER_BACK)) model_harness_slider_back();
  299. }
  300. module model() {
  301. phone_harness();
  302. }
  303. if (!is_undef(validation)) {
  304. intersection() {
  305. phone();
  306. model();
  307. }
  308. } else {
  309. intersection() {
  310. if (!is_undef(xray)) {
  311. if (xray == XRAY_POSITIVE) {
  312. span_cube([0,1000], [0,1000], [-1000,1000]);
  313. } else if (xray == XRAY_BACKPLANE) {
  314. span_cube([-1000,1000], [-1000,1000], [-1000,-7]);
  315. }
  316. }
  317. union() {
  318. if (is_not_export()) {
  319. //phone();
  320. //arm();
  321. }
  322. model();
  323. }
  324. }
  325. }