chiark / gitweb /
01796a134896a9d53289f51b7f635abdb286555b
[reprap-play.git] / fairphone-case.scad
1 // -*- C -*-
2
3 include <utils.scad>
4
5 phone = [ 75.0, 145.0 ];
6
7 prop_lid_posns = [ 70 ]; // measured from bottom of phone
8
9 prop_lengths = [ 80, 50 ];
10
11 prop_angle_specs = [
12   // angle    prop length index     prop lid posn index
13   [   15,          1,                    0 ],
14   [   30,          1,                    0 ],
15   [   45,          0,                    0 ],
16   [   60,          0,                    0 ],
17   ];
18
19 bumper = [ 0.250, -0.025 ];
20 // ^ One side.  Overall size is increased by twice this.
21 // If no bumpers, is the gap around the phone.
22
23 enable_support = 1;
24
25 phone_cnr_rad = 6.0;
26
27 button_cutout_depth = 9;
28
29 phone_edge_thick = 9.0;
30 phone_total_thick = 12.0;
31 phone_backside_slope_inner = 1.5; // larger means shallower
32 phone_backside_slope_outer = 1.0; // larger means shallower
33
34 camera_pos_tl = [  6.450, 12.750 ]; // measured from tl corner
35 camera_pos_br = [ 22.300, 37.600 ]; // tl/br as seen from back
36
37 jack_pos = [ 14.38, 7.96 ];
38 jack_dia = 10.64 + .5; // some jack I had lying around
39
40 noisecancelmic_pos = [ 19.54, 7.37 ];   // from rhs
41 noisecancelmic_dia = 4.00;
42
43 //fingerpushhole_dias = [ 15, 18 ];
44 fingerpushhole_dias = [];
45
46 rearspeaker_pos_bl = [ 12.64, 18.72 ];
47 rearspeaker_size   = [  3.76,  7.36 ];
48
49 microusb_above = 3.27 - 0.25;
50 microusb_below = 0.0;
51 microusb_width = 16.12 + 1.25;
52
53 case_th_bottom = 2.5;
54 case_th_lid = 2.5;
55 case_th_side = 2;
56 case_th_lip = 1.2;
57
58 case_struts_count = 6;
59 case_struts_solid_below = 1.00;
60 case_struts_solid_above = 0.75;
61 case_struts_width = 0.10;
62
63 keeper_th_z = 0.75;
64 keeper_th_x = 0.75;
65 keeper_inner_width = 2.75;
66 keeper_inner_height = 2.75;
67 keeper_slant_slope = 2; // larger means steeper
68
69 keeper_gap_z_top = 0.25;
70 keeper_gap_z_bot = 0.75;
71 keeper_gap_x     = 0.25;
72 keeper_gap_x_holes = 0.75;
73
74 keeper_side = 0; // 0 = lhs; 1 = rhs
75
76 case_lip = 1.25;
77
78 lid_gap_x = 0.25;
79 lid_gap_z = 0.25;
80 lid_lip = 1.75;
81
82 catch_slop = 0.50;
83
84 foldover_gap = 0.50;
85 foldover_lever_gap = 0.50;
86
87 hingescrew_shaft_dia = 2.0 + 0.25; // M2 x 12mm machine screw
88 hingescrew_shaft_len = 12;
89 hingescrew_nut_thick = 1.93 + 0.20; // incl. washer
90 hingescrew_nut_dia = 4.72 + 0.50; // washer, actually
91 hingescrew_head_th = 1.38 + 0.75;
92 hingescrew_head_dia = 3.92;
93 lever_cover_th = 0.75;
94 hingemount_th = 2.5;
95
96 $fa = 5;
97 $fs = 0.1;
98
99 button_l_fudge = 4.4;
100 buttonishleg_default_l_is_fudge = 10;
101
102 hinge_base_slope = 1.5; // bigger is steeper
103
104 strut_min_at_end = 1.5;
105
106 hinge_x_gap = 0.125;
107 hinge_x_postscrew_gap = 0.75;
108 hinge_x_arms_gap = 0.35;
109 hinge_r_arms_gap = 0.55;
110
111 rearspeaker_gap    = [ 2.0, 2.0 ]; // each side
112
113 catch_len = 7.5;
114 catch_width = 15;
115 catch_thickness = 1.0;
116 catch_side_gap = 0.75; // each side
117
118 catch_depth = 0.75;
119 catch_height = 0.35;
120 catch_finger_height = 1.5;
121 catch_finger_depth = 2.5;
122
123 prop_main_width = 3;
124 prop_wing_len = 3;
125 prop_nose_slope = 1.5; // bigger means pointier
126 prop_side_gap = 0.75; // each side
127 prop_fin_height = 1.5;
128 prop_fin_width = 0.75;
129 prop_max_angle = 45; // bigger means at worse angle
130 prop_recess_under = 0.30;
131 prop_backfwd_gap = 1.0; // total
132
133 prop_recess_slop = 0.125; // each side
134 prop_end_dia = 0.5;
135 prop_main_th = 3;
136 prop_taper_len = 6;
137
138 // ---------- calculated ----------
139
140 phone_width =  (phone + bumper*2)[0];
141 phone_height = (phone + bumper*2)[1];
142
143 inside_br = [phone_width, -phone_height];
144
145 //echo(camera_pos_tl + bumper,
146 //     camera_pos_br + bumper);
147
148 // ----- could be changed -----
149 lid_buttoncover_gap = lid_gap_x;
150 lid_buttoncover_overlap = case_th_lip + keeper_gap_z_top;
151
152 phone_backside_slope_thick = phone_total_thick - phone_edge_thick;
153
154 prop_nose_len = case_th_lid - prop_recess_under;
155 prop_recess_slope = tan(prop_max_angle); // bigger means steeper
156 prop_recess_depth = case_th_lid - prop_recess_under;
157 prop_recess_width = prop_main_th / cos(prop_max_angle) + prop_backfwd_gap;
158
159 //lid_lip_overlap_width xxx bad name = ;
160 //lid_lip_inner_slope = [ 5, 5 ]; // xxx
161
162 epp0 = [0,0];
163 epp1 = [0, -phone_edge_thick];
164 epp2i = epp1 + phone_backside_slope_thick * [ phone_backside_slope_inner, -1 ];
165 epp2o = epp1 + phone_backside_slope_thick * [ phone_backside_slope_outer, -1 ];
166 epp3 = epp2i + [10, 0];
167 epp5 = epp0 + [0,1] * (keeper_th_z + keeper_gap_z_top + case_lip);
168 epp4 = epp5 + [-1,0] * case_th_side;
169
170 kppe = [0,0];
171 kppd = kppe + [1,0] * keeper_inner_width;
172 kppc = kppd + [0,1] * keeper_th_z;
173 kppb = [ kppe[0] - keeper_th_x, kppc[1] ];
174 kppf = kppe - [0,1] * keeper_inner_height;
175 kppa = [ kppb[0], kppf[1] ];
176
177 lpp10 = [ epp5[0] + lid_gap_x, kppc[1] + lid_gap_z ];
178 lpp11 = [ lpp10[0],            epp5[1] + lid_gap_z ];
179
180 lp_r12 = case_th_lid - (lpp11[1] - lpp10[1]);
181
182 lpp12 = [ epp4[0] + lp_r12,    lpp11[1] ];
183 lpp13 = [ lpp12[0],            lpp12[1] + lp_r12 ];
184
185 // button profile
186 bppM = epp4 + [0,5];
187 bppN = [ 0.5 * (epp0[0] + epp4[0]), bppM[1] ];
188 bppR = [ bppN[0] + lid_buttoncover_gap, -button_cutout_depth ];
189 bppS = [ epp1[0], bppR[1] ];
190 bppQ = [ bppM[0], bppR[1] - lid_buttoncover_overlap ];
191 bppP = bppQ + [0,1] * lid_buttoncover_gap;
192 bppO = [ bppN[0], bppP[1] ];
193 bppL = lpp10 + [5,0];
194 bppK = [ bppL[0], bppN[1] ];
195 bppJ = [ bppN[0], bppL[1] ];
196
197 // hinge plan
198 hp_rn = hingescrew_nut_dia/2;
199 hp_r2_min = hp_rn + lever_cover_th;
200 hp_rs = hingescrew_shaft_dia/2;
201 hp_r1_min = hp_rs + hingemount_th;
202
203 hp_r1 = max(hp_r1_min, hp_r2_min);
204 hp_r2 = hp_r1;
205
206 hppU = lpp13;
207 hppS = epp2o + [0,-1] * case_th_bottom;
208 hp_k = 0.5 * (hppU[1] - hppS[1] + foldover_gap);
209
210 hppM = [ epp4[0] - foldover_lever_gap - hp_r2,
211          0.5 * (hppU + hppS)[1] ];
212 hppT = [ hppM[0], hppU[1] - hp_r1 ];
213 hppB = hppT + [0,-1] * hp_k;
214
215 hppE_y = epp2o[1] - case_th_bottom + hp_r1;
216 hppE_x = hppB[0] + (hppB[1] - hppE_y) * hinge_base_slope;
217 hppE = [ hppE_x, hppE_y ];
218
219 // hinge elevation x coords
220
221 hingescrew_portion_len =
222   0.5* (hingescrew_shaft_len - hingescrew_nut_thick - hinge_x_gap);
223
224 hex20 = max(epp2o[0],
225             phone_cnr_rad,
226             kppd[0] + hingescrew_head_th + keeper_gap_x_holes);
227 hex21 = hex20 + hingescrew_portion_len;
228 hex22 = hex21 + hinge_x_gap;
229 hex23 = hex22 + hingescrew_portion_len
230   - hingescrew_nut_thick; // bodge, need to divvy this up more sensibly
231 hex24 = hex20 + hingescrew_shaft_len + hinge_x_postscrew_gap;
232 //echo(hex20, hex21, hex22, hex23, hex24);
233 //echo(hingescrew_portion_len);
234
235 // catch
236
237 cppJ = [ epp4[0] + catch_thickness, lpp10[1] ];
238 cppA = cppJ + [lid_gap_x, -lid_gap_z];
239 cppB = [ epp0[0], cppA[1] ];
240 cppP = [ epp4[0], cppJ[1] ];
241
242 cppS = cppJ + [0,-1] * catch_len;
243 cppD = [ cppA[0], cppS[1] + catch_slop ];
244 cppC = [ cppB[0], cppD[1] ];
245 cppT = cppS + [1,0] * catch_depth;
246 cppU = cppT + [0,-1] * catch_height;
247 cppV = [ cppS[0], cppU[1] - catch_depth ];
248
249 cppR = 0.5*(cppP + cppJ);
250
251 cp_rQ = 0.5 * (cppJ[0] - cppP[0]);
252 cppQ = [ cppR[0],
253          cppV[1] - (catch_finger_height - cp_rQ) ];
254 cppF = [ cppV[0] + catch_finger_depth, cppC[1] ];
255
256 // prop recess
257
258 prp1 = [0,0]; // by definition
259 prp2 = prp1 + [ -prop_recess_slope, -1 ] * prop_recess_depth;
260 prp4 = prp1 + [1,0] * prop_recess_width;
261 prp3 = [ prp4[0], prp2[1] ];
262 prp5 = prp4 + [1,0] * prop_recess_depth;
263
264 // prop recess in lid
265
266 prlp1 = [ lpp10[0] + prop_recess_slop + prop_end_dia/2,
267           lpp10[1] + prop_recess_slop + prop_end_dia/2 ];
268
269 // ---------- modules ----------
270
271 module KeeperProfile(slant=0){
272   use_e = kppe + [0,-1] * slant * keeper_inner_width / keeper_slant_slope;
273   polygon([use_e, kppd, kppc, kppb, kppa, kppf]);
274 }
275
276 module EdgeProfile(){
277   difference(){
278     hull(){
279       translate(epp3) square(case_th_bottom*2, center=true);
280       circleat(epp2o, r=case_th_bottom);
281       circleat(epp1, r=case_th_side);
282       rectfromto(epp0, epp4);
283     }
284     polygon([ epp5 + [0,10],
285               epp1,
286               epp2i,
287               epp3 + [10,0] ]);
288   }
289 }
290
291 module LidEdgeProfile(){
292   polygon([ lpp10,
293             lpp11,
294             lpp12,
295             lpp13,
296             lpp13 + [10, 0],
297             lpp10 + [10, 0]
298             ]);
299   intersection(){
300     circleat(lpp12, r=lp_r12);
301     rectfromto( lpp12 + [-10,   0],
302                 lpp12 + [+10, +10] );
303   }
304 }
305
306 module ButtonCoverProfile(){
307   intersection(){
308     polygon([ bppM, bppP, bppO, bppJ, bppL, bppK ]);
309     hull(){
310       EdgeProfile();
311       LidEdgeProfile();
312     }
313   }
314 }
315
316 module ButtonPlan(l, deep, cut){
317   epsilon =
318     (cut  ? 0 : lid_buttoncover_gap);
319
320   delta =
321     (deep ? lid_buttoncover_overlap : 0);
322
323   C = [0,0]; // by definition
324   T = [ 0, epp4[1] ];
325   G = T + [0,10];
326
327   B0 = C + [0,-1] * button_cutout_depth;
328   B1 = B0 + [0,1] * epsilon;
329
330   r0 = 0.5 * (T[1] - B0[1]);
331   A = [  -(l + button_l_fudge)/2 + r0, 0.5 * (T[1] + B0[1]) ];
332   H = A + [0,-1] * delta;
333
334   D = A + [-2,0] * r0;
335   F = D + [0,10];
336
337   E0 = 0.5 * (D + A);
338   E1 = E0 + [1,0] * epsilon;
339
340   I0 = [ E0[0], H[1] ];
341   I1 = [ E1[0], H[1] ];
342
343   hull(){
344     for (m=[0,1]) mirror([m,0])
345       circleat(H, r0 - epsilon);
346   }
347   for (m=[0,1]) mirror([m,0]) {
348     difference(){
349       polygon([ E1,
350                 I1,
351                 H,
352                 B1,
353                 G,
354                 F,
355                 D
356                 ]);
357       circleat(D, r0 + epsilon);
358     }
359   }
360 }
361
362 module CatchCatchProfile(){
363   hull(){
364     for (c=[ cppR, cppQ ])
365       circleat(c, cp_rQ);
366   }
367   hull(){
368     circleat(lpp12, lp_r12);
369     circleat(lpp12 + [5,0], lp_r12);
370     rectfromto(cppP, cppP + [5,0.1]);
371   }
372   polygon([cppJ, cppS, cppT, cppU, cppV, cppQ, cppR]);
373 }
374
375 module CatchCutProfile(){
376   polygon([ cppB,
377             cppA,
378             cppD,
379             cppF,
380             cppF + [0,-10],
381             cppF + [-10,-10],
382             lpp12 + [-10,0],
383             lpp12 + [10,0]
384             ]);
385 }
386
387 module Flip_rhs(yn=[0,1]) {
388   for ($rhsflip=yn) {
389     translate([phone_width/2, 0, 0])
390       mirror([$rhsflip,0,0])
391       translate([-phone_width/2, 0, 0])
392       children();
393   }
394 }
395
396 module Flip_bot(yn=[0,1]) {
397   for ($botflip=yn) {
398     translate([0, -phone_height/2, 0])
399       mirror([0, $botflip, 0])
400       translate([0, phone_height/2, 0])
401       children();
402   }
403 }  
404
405 module AroundEdges(fill_zstart, fill_th, fill_downwards=0){
406   // sides
407   Flip_rhs(){
408     translate([0, -phone_cnr_rad, 0])
409       rotate([90,0,0])
410       linear_extrude(height = phone_height - phone_cnr_rad*2)
411       children(0);
412   }
413   // corners
414   Flip_rhs() Flip_bot() {
415     translate([+1,-1] * phone_cnr_rad)
416       intersection(){
417         rotate_extrude()
418           intersection(){
419             mirror([1,0,0])
420               translate([-1,0] * phone_cnr_rad)
421               children(0);
422             rectfromto([0,-20],[10,20]);
423           }
424         translate([-10, 0, -20] + 0.01 * [+1,-1, 0] )
425           cube([10,10,40]);
426       }
427   }
428   // top and bottom
429   Flip_bot(){
430     translate([ phone_width - phone_cnr_rad, 0,0 ])
431       rotate([90,0,-90])
432       linear_extrude(height = phone_width - phone_cnr_rad*2)
433       children(0);
434   }
435   // fill
436   translate([0,0, fill_zstart])
437     mirror([0,0, fill_downwards])
438     linear_extrude(height = fill_th)
439     rectfromto([+1,-1] * phone_cnr_rad,
440                [phone_width, -phone_height] + [-1,+1] * phone_cnr_rad);
441 }
442
443 module CaseAperture(pos, dia, $fn) {
444   theta = 180/$fn;
445   translate([ pos[0] + bumper[0],
446               -epp2i[0],
447               -pos[1] ])
448     rotate([-90, theta, 0])
449     cylinder(r = dia/2 / cos(theta),
450              h = 60);
451 }
452
453 module SideButton(y, y_ref_sign, l){
454   // y_ref_sign:
455   //   +1  measured from top    of actual phone to top    of button
456   //   -1  measured from bottom of actual phone to bottom of button
457   //    0  y is centre of button in coordinate system
458   $button_l= l;
459   eff_y = y_ref_sign > 0 ?         -bumper [1] -y -l/2 :
460           y_ref_sign < 0 ? (-phone -bumper)[1] +y +l/2 :
461           y;
462   //echo(eff_y);
463   translate([0, eff_y, 0])
464     children();
465 }
466
467 module LidButtonishLeg(y, y_ref_sign, l=buttonishleg_default_l_is_fudge) {
468   $button_leg_only = true;
469   SideButton(y, y_ref_sign, l) children();
470 }
471
472 module Buttons(){
473   Flip_rhs(1) SideButton(15.580, +1, 8.9) children(); // power
474   Flip_rhs(1) SideButton(48.700, -1, 8.920) children(); // camera
475   Flip_rhs(0) SideButton(30.800, +1, 21.96) children(); // volume
476   Flip_rhs(   ) LidButtonishLeg(14, -1) children();
477 //  Flip_rhs(0) LidButtonishLeg(20, +1, 20) children();
478 }
479
480 module Struts(x_start, z_min, th){
481   // if th is negative, starts at z_min and works towards -ve z
482   // and object should then be printed other way up
483   for (i= [1 : 1 : case_struts_count]) {
484     translate([0,
485                0,
486                z_min])
487       mirror([0,0, th<0 ? 1 : 0])
488       translate([0,
489                  -phone_height * i / (case_struts_count+1),
490                  case_struts_solid_below])
491       linear_extrude(height= abs(th)
492                      -(case_struts_solid_below+case_struts_solid_above))
493       rectfromto([               x_start, -0.5 * case_struts_width ],
494                  [ phone_width - x_start, +0.5 * case_struts_width ]);
495   }
496 }
497
498 module OrdinaryRearAperture(rhs,bot, pos){
499   Flip_rhs(rhs) Flip_bot(bot)
500     linextr(-20, 20)
501     mirror([0,1])
502     translate(pos + bumper)
503     children();
504 }
505
506 module MicroUSB(){
507   Flip_bot(1){
508     rotate([90,0,0])
509       mirror([0,0,1])
510       linextr(-epp2i[0], 60)
511       translate([0.5 * phone_width, 0, 0])
512       rectfromto([-microusb_width/2, epp2i[1] + microusb_below],
513                  [+microusb_width/2, epp0[1] + -microusb_above]);
514   }
515 }
516
517 module OrdinaryRearApertures(){
518   // rear speaker
519   OrdinaryRearAperture(1,1, rearspeaker_pos_bl)
520     rectfromto(-rearspeaker_gap,
521                rearspeaker_size + rearspeaker_gap);
522
523   // finger hole to remove phone
524   if (len(fingerpushhole_dias))
525     OrdinaryRearAperture(1,0, [ fingerpushhole_dias[0]/2 + epp2i[0],
526                                 phone[1]/2 ])
527     scale(fingerpushhole_dias)
528     circle(r= 0.5 );
529 }
530
531 module RearCameraAperture(){
532   Flip_rhs(1)
533     mirror([0, 0, 1])
534     linear_extrude(height = 20)
535     mirror([0, 1, 0])
536     translate(bumper)
537     rectfromto(camera_pos_tl, camera_pos_br);
538 }
539
540 module HingeLidProfile(){
541   hull(){
542     circleat(hppT, hp_r1);
543     circleat(lpp12, lp_r12);
544     polygon([lpp10,
545              lpp13 + [2,0],
546              lpp12,
547              hppT]);
548   }
549 }
550
551 module HingeBaseProfile(){
552   difference(){
553     hull(){
554       circleat(hppB, hp_r1);
555       circleat(hppE, hp_r1);
556       circleat(epp2o, case_th_bottom);
557       circleat(hppB + [10,0], hp_r1);
558     }
559     polygon([epp5, epp1, epp2i, epp3, bppL]);
560   }
561 }
562
563 module HingeLeverOuterProfile(){
564   hull(){
565     circleat(hppT, hp_r2);
566     circleat(hppB, hp_r2);
567   }
568 }
569
570 module HingeLeverInnerProfile(){
571   for (c = [hppT, hppB]) {
572     hull()
573       for (x=[-20,20])
574         for (y=[0, c[1] - hppM[1]])
575           translate([x,y])
576             circleat(c, hp_rn);
577   }
578 }
579
580 module Flip_hinge(){
581   hinge_origin = [0, -(phone_height - hppB[0]), hppB[1]];
582   translate(hinge_origin)
583     rotate([180,0,0])
584     translate(-hinge_origin)
585     children();
586 }
587
588 module HingePortion(x0,x1){
589   Flip_rhs() Flip_bot(1)
590     translate([x0,0,0])
591     mirror([1,0,0])
592     rotate([90,0,-90])
593     linear_extrude(height=x1-x0)
594     children(0);
595 }
596
597 module CatchPortion(width){
598   translate([phone_width/2, 0,0])
599     rotate([90,0,-90])
600     linextr(-width/2, width/2)
601     children(0);
602 }
603
604 module PropRecess(){
605   // origin is nonstandard
606   hwd345 = 0.5*prop_main_width + prop_side_gap;
607   hwd35  = 0.5*prop_fin_width + prop_side_gap;
608   rotate([90,0,90]){
609     linextr(-hwd345, +hwd345)
610       polygon([ prp1,
611                 prp2,
612                 prp3,
613                 prp4,
614                 prp4 + [0,10],
615                 prp1 + [0,10],
616                 ]);
617     linextr(-hwd35, +hwd35)
618       polygon([ prp1,
619                 prp3,
620                 prp5,
621                 prp5 + [0,10],
622                 prp1 + [0,10],
623                 ]);
624   }
625 }
626
627 module SomePropProfile(gamma){
628 // prop-internal
629   pip1 = [0,0];
630   pip2 = [ 0.5 * (prop_main_th - prop_end_dia),
631            prop_taper_len ];
632   
633   translate(prlp1) {
634     circleat([0,0], prop_end_dia/2);
635     cirecleat(pip2, prop_main_th);
636     
637   }
638 }
639
640 module CasePropRecess(pas) {
641   // destructure entry in prop_angle_specs
642   beta =                 pas[0];
643   b    = prop_lengths  [ pas[1] ];
644   p    = prop_lid_posns[ pas[2]] ;
645
646   k = hp_k;
647   x = k / (2 * sin(beta/2)) - hppT[0];
648   c = p + x;
649
650   // https://en.wikipedia.org/wiki/Solution_of_triangles#Two_sides_and_non-included_angle_given_(SSA) [25.1.18]
651   // we always want the positive solution because it has a closer to c
652   a = c * cos(beta) + sqrt( b*b - pow(c * sin(beta),2) );
653   q = a - x;
654
655   echo(beta, a, b, c);
656
657   Flip_bot(1)
658     translate([ phone_width/2, -q, epp2o[1]-case_th_bottom ])
659     rotate([0,180,0])
660     PropRecess();
661 }
662
663 module CaseBase(){
664   AroundEdges(epp3[1], case_th_bottom, 1)
665     EdgeProfile();
666 }
667
668 module Case(){ ////toplevel
669   difference(){
670     union(){
671       CaseBase();
672
673       // ledge (fixed keeper)
674       Flip_rhs(1-keeper_side) intersection(){
675         rotate([90, 0, 0])
676           linear_extrude(height = phone_height + phone_cnr_rad * 2)
677           KeeperProfile(1);
678
679         // outline of the whole case, to stop it protruding
680         translate([0,0, -25])
681           linear_extrude(height = 50)
682           hull()
683           Flip_bot()
684           circleat([+1,-1] * phone_cnr_rad, phone_cnr_rad + case_th_side/2);
685       }
686
687       // hinge
688       HingePortion(hex20, hex21) HingeBaseProfile();
689     }
690
691     // slot for keeper
692     Flip_rhs(keeper_side)
693       translate([0, -phone_cnr_rad, 0])
694       rotate([90, 0, 0])
695       linear_extrude(height = phone_height + phone_cnr_rad * 2)
696       minkowski(){
697         KeeperProfile();
698         rectfromto([ -keeper_gap_x,    -keeper_gap_z_bot ],
699                    [ keeper_gap_x_holes,    +keeper_gap_z_top ]);
700       }
701
702     // front camera
703     RearCameraAperture();
704
705     // struts (invisible, because they're buried in the case)
706     Struts(epp2i[0], epp2i[1] - case_th_bottom, case_th_bottom);
707
708     Buttons(){
709       mirror([1,0,0])
710         rotate([90,0,90]) {
711           if (!($button_leg_only && enable_support))
712           intersection(){
713             translate([0,0,-10])
714               linear_extrude(height= 20)
715               ButtonPlan($button_l, 0,1);
716             if ($button_leg_only)
717               rotate([-90,90,0])
718                 translate([phone_width/2, -400, kppe[1]])
719                 mirror([1-abs($rhsflip - keeper_side),0,0])
720                 cube([400, 800, 50]);
721             if (enable_support)
722               rotate([-90,90,0])
723               translate([-400, -400, kppd[1]])
724                 mirror([0,0,1])
725                 cube([800,800,100]);
726           }
727           translate([0,0, -bppR[0]])
728             linear_extrude(height= 20)
729             ButtonPlan($button_l, 1,1);
730         }
731     }
732
733     // apertures along top edge
734     CaseAperture(jack_pos, jack_dia, 8);
735     Flip_rhs(1)
736       CaseAperture(noisecancelmic_pos, noisecancelmic_dia, 8);
737
738     OrdinaryRearApertures();
739
740     MicroUSB();
741
742     // gaps for the lid's hinge arms
743     HingePortion(hex20 - hinge_x_arms_gap,
744                  hex21 + hinge_x_arms_gap)
745       minkowski(){
746         HingeLidProfile();
747         circle(r= hinge_r_arms_gap, $fn= 8);
748       }
749
750     // screw holes in the hinge arms
751     HingeScrews();
752
753     // catch striker
754     CatchPortion(catch_width + catch_side_gap*2)
755       CatchCutProfile();
756
757     // prop recesses
758     #for (pas = prop_angle_specs)
759       CasePropRecess(pas);
760   }
761 }
762
763 module Lid(){ ////toplevel
764   difference(){
765     union(){
766       AroundEdges(lpp10[1], lpp13[1] - lpp10[1], 0)
767         LidEdgeProfile();
768
769       // button covers
770       Buttons(){
771         intersection(){
772           rotate([90,0,90])
773             translate([0,0,-10])
774             linear_extrude(height= 20)
775             ButtonPlan($button_l, 1,0);
776           rotate([90,0,0])
777              translate([0,0,-100])
778             linear_extrude(height= 200)
779             ButtonCoverProfile();
780         }
781       }
782
783       // hinge arms
784       HingePortion(hex20, hex21) HingeLidProfile();
785
786       // catch
787       CatchPortion(catch_width)
788         CatchCatchProfile();
789     }
790     Struts(lpp10[0] + strut_min_at_end, lpp13[1], -case_th_lid);
791
792     // screw holes in the hinge arms
793     HingeScrews();
794
795     // prop recesses
796     Flip_bot(1)
797       for (y = prop_lid_posns)
798         translate([ phone_width/2, -y, lpp13[1] ])
799           PropRecess();
800   }
801 }
802
803 module HingeLever(){ ////toplevel
804   difference() {
805     // outer body, positive
806     HingePortion(hex22, hex22 + phone_width/2)
807       HingeLeverOuterProfile();
808
809     // space for the screws
810     HingePortion(hex23, hex24)
811       HingeLeverInnerProfile();
812
813     // bores for the screws
814     HingeScrews();
815
816     // space for the charging cable
817     MicroUSB();
818     Flip_hinge() MicroUSB();
819   }
820 }
821
822 module HingeLeverPrint(){ ////toplevel
823   rotate([90,0,0])
824     HingeLever();
825 }
826
827 module TestSelectLength(){
828   translate([-30, -200, -20])
829     cube([30 + 15, 250, 40]);
830 }
831
832 module TestLength(){ ////toplevel
833   intersection(){
834     Case();
835     TestSelectLength();
836   }
837 }
838
839 module TestLengthRight(){ ////toplevel
840   intersection(){
841     Case();
842     Flip_rhs(1)
843       TestSelectLength();
844   }
845 }
846
847 module TestSelectWidth(){
848   translate([-30, -(phone_height - 25), -20])
849     mirror([0, 1, 0])
850     cube([200, 50, 40]);
851 }
852
853 module TestWidth(){ ////toplevel
854   intersection(){
855     Case();
856     TestSelectWidth();
857   }
858 }
859
860 module TestLidWidthPrint(){ ////toplevel
861   rotate([0,180.0]) intersection(){
862     Lid();
863     TestSelectWidth();
864   }
865 }
866
867 module TestSelectRearAperture(){
868   minkowski(){
869     union() children();
870     translate([20, 0,0])
871       cube([42, 2, 1], center=true);
872   }
873 }
874
875 module TestSelectCamera(){
876   minkowski(){
877     TestSelectRearAperture()
878       RearCameraAperture();
879     cube([0.1, 50, 0.1]);
880   }
881 }
882
883 module TestSelectOrdinaryRearApertures(){
884   TestSelectRearAperture()
885     OrdinaryRearApertures();
886 }
887
888 module TestCamera(){ ////toplevel
889   intersection(){
890     Case();
891     TestSelectCamera();
892   }
893 }
894
895 module TestLidByCamera(){ ////toplevel
896   intersection(){
897     Lid();
898     TestSelectCamera();
899   }
900 }
901
902 module TestLidByCameraPrint(){ ////toplevel
903   rotate([180,0,0]) TestLidByCamera();
904 }
905
906 module DemoByCamera(){ ////toplevel
907   color("blue") TestLidByCamera();
908   color("red")  TestCamera();
909 }
910
911 module OneKeeper(){ ////toplevel
912   translate([0, -phone_cnr_rad, 0])
913     rotate([90, 0, 0])
914     linear_extrude(height = phone_height - phone_cnr_rad * 2)
915     KeeperProfile();
916 }
917
918 module OneKeeperPrint(){ ////toplevel
919   rotate([0,180,0])
920     OneKeeper();
921 }
922
923 module LidPrint(){ ////toplevel
924   rotate([0,180,0])
925     Lid();
926 }
927
928 module TestSelectPropRecesses(posns){
929   linextr(-100,100){
930     translate([phone_width/2, -phone_height]){
931       square([ 3, 500 ], center=true);
932       for (y=posns)
933         hull()
934           for (dy=[ -prp5[0], -prp2[0] ])
935             translate([0,y+dy])
936               square([prop_main_width+5, 5], center=true);
937     }
938   }
939 }
940
941 module TestSelectFrame(){
942   include = [1,-1] * (epp2i[0] + 4);
943
944   difference(){
945     cube(1000, center=true);
946     translate([0,0, -100])
947       linear_extrude(height=200)
948       rectfromto(include,  inside_br - include);
949   }
950 }
951
952 module TestSelectLidFrame(){
953   TestSelectFrame();
954   TestSelectPropRecesses(prop_lid_posns);
955 }
956
957 module TestFrameCase(){ ////toplevel
958   intersection(){
959     Case();
960     union(){
961       TestSelectFrame();
962       TestSelectCamera();
963       TestSelectOrdinaryRearApertures();
964     }
965   }
966 }
967
968 module TestFrameLidPrint(){ ////toplevel
969   rotate([0,180,0]) intersection(){
970     Lid();
971     TestSelectLidFrame();
972   }
973 }
974
975 module ButtonPlanForDemo(z, deep, cut){
976   translate([0,0,z])
977     ButtonPlan(8, deep, cut);
978 }
979
980 module HingeScrews(){
981   Flip_rhs() Flip_bot(1){
982     for (c= [ hppT, hppB ])
983       translate([ hex20,
984                   -c[0],
985                   c[1] ]){
986         rotate([0,90,0])
987           translate([0,0,-.2])
988           cylinder( r= hingescrew_shaft_dia/2,
989                     h = hingescrew_shaft_len+0.2 );
990         rotate([0,-90,0])
991           translate([0,0,+.1])
992           cylinder( r= hingescrew_head_dia/2, h = hingescrew_head_th );
993       }
994   }
995 }
996
997 module DemoFrame(){ ////toplevel
998   color("red") TestFrameCase();
999   color("blue") intersection(){ Lid(); TestSelectLidFrame(); }
1000   color("black") HingeScrews();
1001   %HingeLever();
1002 }
1003
1004 module DemoHingedFrame(){ ///toplevel
1005   color("red") TestFrameCase();
1006   translate([0,0, -2*hp_k])
1007   color("blue") intersection(){ Lid(); TestSelectLidFrame(); }
1008
1009   Flip_hinge(){
1010     color("orange") HingeLever();
1011     color("black") HingeScrews();
1012   }
1013 }
1014
1015 module DemoHinge(){ ////toplevel
1016   DemoFrame();
1017   translate([0,0, -hp_k*3])
1018     DemoHingedFrame();
1019 }
1020
1021 module DemoProfiles(){ ////toplevel
1022   LidEdgeProfile();
1023   %EdgeProfile();
1024   KeeperProfile();
1025   translate([0,0,-1]) color("black") KeeperProfile(1);
1026
1027   translate([20,0]) {
1028     LidEdgeProfile();
1029     %EdgeProfile();
1030
1031     demopoint_QR = [ bppS[0], bppQ[1] - 0.1];
1032   
1033     color("blue") ButtonCoverProfile();
1034     color("red") {
1035       rectfromto(bppQ, demopoint_QR);
1036       rectfromto(bppR, demopoint_QR);
1037     }
1038   }
1039
1040   translate([-20,0]) {
1041     color("black") ButtonPlanForDemo(-2, 0,1);
1042     color("red" )  ButtonPlanForDemo(-4, 1,1);
1043     color("blue")  ButtonPlanForDemo(-6, 1,0);
1044   }
1045
1046   translate([0, -30]) {
1047     %LidEdgeProfile();
1048     %EdgeProfile();
1049     color("blue") HingeLidProfile();
1050     color("red")  HingeBaseProfile();
1051     color("black") translate([0,0,-2]) HingeLeverOuterProfile();
1052     color("orange") translate([0,0,-1]) HingeLeverInnerProfile();
1053   }
1054
1055   translate([20,-30]) {
1056     %EdgeProfile();
1057     %LidEdgeProfile();
1058     //translate([0,0,1]) CatchCutProfile();
1059     color("blue") CatchCatchProfile();
1060     color("red") difference(){ EdgeProfile(); CatchCutProfile(); }
1061   }
1062 }
1063
1064 //EdgeProfile();
1065 //KeeperProfile();
1066 //CaseBase();
1067 //%Case();
1068 //Keeper();
1069 //LidEdgeProfile();
1070 //KeeperProfile();
1071 //DemoProfiles();
1072 //PropRecess();