chiark / gitweb /
fairphone5-case: fork from fairphone4-case
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 30 Jun 2025 14:56:39 +0000 (15:56 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 30 Jun 2025 15:15:01 +0000 (16:15 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
Makefile
fairphone5-case-coarse.scad [new file with mode: 0644]
fairphone5-case-mounted.scad [new file with mode: 0644]
fairphone5-case-tripod.scad [new file with mode: 0644]
fairphone5-case.scad [new file with mode: 0644]

index 53999865a0d35c537c871893cc3eccde68114c38..7d936e061c88f087b5bbb5b0bec7d639644c15aa 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ FILAMENTSPOOL_AUTOS += filamentspool-storarm3
 QUACKSES = $(addprefix quacks-ingredients-L, 1 2 3 4 5)
 QUACKS_SCADS = $(addsuffix .scad, $(QUACKSES))
 
-USING_AUTOS ?= $(FILAMENTSPOOL_AUTOS) xeno-drivebay-bracket dungeonquest-cone anke-gps-bracket cable-hole-trunking-cover anglepoise-neck crossbar-computer-led-mount wardrobe-hook knifeblock pandemic-counter pattress-boxes-3-cover bike-lipo-box earring-stand bike-stalk-led-mount sewing-table sewing-table-test sewing-table-jig maglite-holder poster-tube-lid poster-tube-lid-coarse fairphone-case fairphone-battery-case fairphone4-case fairphone4-case-coarse lock-inframe-bracket ksafe-base $(QUACKSES) quacks-ingredients-demos mic-table-clamp nook-case nook-case-test scaffold-clamp-common scaffold-clamp-tensioner scaffold-clamp-linear-bracket scaffold-clamp-straphook powerbank-bike-clamp topeak-mtx-tortec-expeditionrack-adapter lipo-flat-mount laptop-sound-cable-hooks digispark-with-cable chimney-cable-retainer mudguard-bracket waring-blender-motor-coupler toolbox-inserts toolbox-inserts-coarse toolbox-inserts-magnet-embed-test $(foreach x,500 1000,adafruit-powerboost-$x)
+USING_AUTOS ?= $(FILAMENTSPOOL_AUTOS) xeno-drivebay-bracket dungeonquest-cone anke-gps-bracket cable-hole-trunking-cover anglepoise-neck crossbar-computer-led-mount wardrobe-hook knifeblock pandemic-counter pattress-boxes-3-cover bike-lipo-box earring-stand bike-stalk-led-mount sewing-table sewing-table-test sewing-table-jig maglite-holder poster-tube-lid poster-tube-lid-coarse fairphone-case fairphone-battery-case fairphone4-case fairphone4-case-coarse fairphone5-case fairphone5-case-coarse lock-inframe-bracket ksafe-base $(QUACKSES) quacks-ingredients-demos mic-table-clamp nook-case nook-case-test scaffold-clamp-common scaffold-clamp-tensioner scaffold-clamp-linear-bracket scaffold-clamp-straphook powerbank-bike-clamp topeak-mtx-tortec-expeditionrack-adapter lipo-flat-mount laptop-sound-cable-hooks digispark-with-cable chimney-cable-retainer mudguard-bracket waring-blender-motor-coupler toolbox-inserts toolbox-inserts-coarse toolbox-inserts-magnet-embed-test $(foreach x,500 1000,adafruit-powerboost-$x)
 
 AUTO_INCS += sealing-box.scad sewing-table.scad nutbox.scad \
             powerbank-anker-10000.dxf \
diff --git a/fairphone5-case-coarse.scad b/fairphone5-case-coarse.scad
new file mode 100644 (file)
index 0000000..ac67a3e
--- /dev/null
@@ -0,0 +1,7 @@
+// -*- C -*-
+
+//// toplevels-from:
+include <fairphone5-case.scad>
+
+$fa = 20;
+$fs = 2;
diff --git a/fairphone5-case-mounted.scad b/fairphone5-case-mounted.scad
new file mode 100644 (file)
index 0000000..f95bb4e
--- /dev/null
@@ -0,0 +1,15 @@
+// -*- C -*-
+
+include <bike-phone-mount.scad>
+
+module CaseMounted(){ ////toplevel
+  Case();
+  translate([ phone_width/2,
+             -phone_height/2, epp3[1] - case_th_bottom ])
+    Mount();
+}
+
+//// toplevels-from:
+include <fairphone5-case.scad>
+$suppress_forward_holes = true;
+$suppress_hinge = true;
diff --git a/fairphone5-case-tripod.scad b/fairphone5-case-tripod.scad
new file mode 100644 (file)
index 0000000..00e44cd
--- /dev/null
@@ -0,0 +1,37 @@
+// -*- C -*-
+
+include <camera-mount.scad>
+
+tr_cube_offset = 20;
+tr_cube_sz = [20, 20, 15];
+tr_around = 10;
+
+module Mount(){
+  translate([0,  - tr_cube_sz[1], 0])
+  difference(){
+    translate([0, tr_cube_sz[1]/2 - tr_cube_offset/2, tr_cube_sz[2]/2])
+      cube(tr_cube_sz + [0, tr_cube_offset, 0], center=true);
+    translate([0, tr_cube_sz[1]/2 - tr_cube_offset, 0])
+      rotate([180,0,0])
+      render() CameraMountThread(tr_cube_sz[2] + 1);
+  }
+}
+
+module CaseMounted(){ ////toplevel
+  difference(){
+    render() Case();
+    translate([ phone_width/2, -phone_height/2 ])
+      linextr(-50, 50)
+      square([phone_width, phone_height] - tr_around * 2 * [1,1],
+            center=true);
+  }
+  translate([ phone_width,
+             -phone_height + tr_cube_sz[0] * 0.7,
+             epp3[1] - case_th_bottom ])
+    rotate([0,0,90])
+    Mount();
+}
+
+//// toplevels-from:
+include <fairphone5-case.scad>
+$suppress_hinge = true;
diff --git a/fairphone5-case.scad b/fairphone5-case.scad
new file mode 100644 (file)
index 0000000..0a4b63d
--- /dev/null
@@ -0,0 +1,1801 @@
+// -*- C -*-
+
+// Hard case for Fairphone 2
+//
+//  Copyright 2018 Ian Jackson.  There is NO WARRANTY.
+//  See below for full licensing and disclaimer.
+//
+// Instructions
+//
+//  1. You will want to git clone this repository.
+//
+//  2. <deleted>
+//
+//  3. use "make" to generate the necessary files:
+//
+//     make -j8 fairphone-case.auto.scads `for f in   \
+//        HingeLeverPrint   \
+//        LidPrint          \
+//        OneKeeperPrint    \
+//        Case              \
+//     ; do echo fairphone-case,$f.auto.stl; done`
+//
+//  4. Print them.  Case and OneKeeperPrint should probably be
+//     the same colour.
+//
+//  5. Assemble the hinge.  After placing the parts in the appropirate
+//     relative placement:
+//
+//        Use long bit of wire to ensure holes are lined up and proper
+//        Cut four short bits of wire, using above as a guage
+//
+//        Push two short bits into two holes on same side
+//        Use long bit of wire to ensure properly in holes
+//        Keep that side up so they don't fall out!
+//
+//        For each of the two holes
+//          Use 20-30cm hunk of 2.85mm PLA
+//          Use gas flame to melt end until it catches fire (!)
+//          Remove from flame, wave to extinguish, and quickly:
+//          Dab end onto where hole is
+//          As it congeals, use sidecutters to cut off by hole
+//
+//        Repeat for two holes on other side
+//        When cool, file down rough edges
+//
+//  6. In use:
+// 
+//      - To put the phone in, drop its RH side into the RH side of
+//        the case.  Then feed the keeper through the small hole.
+//        Feed it right through.
+//
+//      - The optional prop can be used to prop the phone up (in
+//        portrait orientation only right now).  See
+//            openscad fairphone-case,DemoPropAngles.auto.scad
+//
+// Other phones
+//
+//  It might well be possible to adapt this file for other phones.
+//  If you do, let me know how you get on.
+//
+//
+// AUTHORSHIP, COPYRIGHT, LICENCE, AND LACK OF WARRANTY
+//
+//   Copyright (C) 2018-2022 Ian Jackson.
+//
+//    This program for generating a 3D model is free software: you can
+//    redistribute it and/or modify it under the terms of the GNU
+//    General Public License as published by the Free Software
+//    Foundation, either version 3 of the License, or (at your option)
+//    any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public
+//    License along with this program.  If not, see
+//    <http://www.gnu.org/licenses/>.
+//
+//  In particular DO NOT BLAME ME IF THIS CASE DOES NOT ADEQUATELY
+//  PROTECT YOUR PHONE !  It is your responsibility to decide whether
+//  this case will meet your needs.
+
+include <utils.scad>
+include <funcs.scad>
+
+phone = [ 75.86, 162.0 ];
+
+prop_buildout_less = 3;
+
+prop_angles = [ 15, 30, 45, 60 ];
+
+bumper = [ 0.250, -0.025 ];
+// ^ One side.  Overall size is increased by twice this.
+// If no bumpers, is the gap around the phone.
+
+enable_support = 1;
+
+led_window_style = 0;
+// 0: no window
+// 1: simply an opening
+// 2: opening with separate cover model, for printing in clear (two colour)
+// 3: like 2 but one-layer window for ad-hoc multi-colour
+
+initial_layer_thick = 0.400; // ^ needed for mode 3 only
+initial_layer_width = 0.750; // ^ needed for mode 3 only
+multicolour_gap = 0.15; // each side
+
+phone_cnr_rad = 7.0; // actuall 8.mumble, but smaller is fine
+phone_rim_depth = 0.01; // includes allowance for a screen protector
+
+button_cutout_depth = 9;
+
+phone_edge_thick = 11.25;
+
+camera_pos_tl = [  5.600,  5.750 ]; // from tl corner (as seen from back)
+camera_edge_rad = 9.750 + 0.700;
+camera_sz = 32.920 + .750 + 1.000;
+
+// this is disabled, FP4 doesn't have one
+jack_pos = [ 13.83, 8.485 ];
+jack_dia = 10.64 + .5; // some jack I had lying around
+
+// this led stuff, is irrelevant, we have disabled it as it doesn't have one
+led_pos = []; // [ 13.98, 10.00 ];
+led_aperture = 9;
+led_window_ledge = 0.75; // each side
+
+noisecancelmic_pos = [ 15.08 + .720, 4.35 ];   // from rhs, from top edge
+noisecancelmic_dia = 4.00;
+
+mainmic_pos = [ 21.0, 4.65 ];   // from lhs, from top edge
+mainmic_dia = 4.00;
+
+lhshole_pos = [ phone[1]/2 + 0.40, 4.35 ];
+
+fingerpushhole_dias = [];
+//fingerpushhole_dias = [ 15, 18 ]; // this is for testing
+
+lanyard_half_dia = 1.15;
+lanyard_entry_rel_breadth = 2;
+lanyard_channel_len = 8;
+//rearspeaker_pos_bl = [ 12.64, 18.72 ];
+//rearspeaker_size   = [  3.76,  7.36 ];
+
+bottomspeaker_size = [ 11.35, 1.90 ] + [1,1] * 0.5;
+bottomspeaker_pos = [ 17.55, 5.17 ]; // from rhs, from top
+
+microusb_above = 1.64 - 0.25;
+microusb_below = 2.42;
+microusb_width = 12.16 + 2.0 + 1.25;
+
+case_th_bottom = 2.5;
+case_th_lid = 3.0;
+case_th_side = 2.6;
+case_th_lip = 1.2;
+
+lid_screen_gap_extra = .66;
+
+case_struts_count = 6;
+case_struts_solid_below = 1.00;
+case_struts_solid_above = 0.75;
+case_struts_width = 0.10;
+
+keeper_th_z = 0.75;
+keeper_th_x = 0.75;
+keeper_inner_width = 2.75;
+keeper_inner_height = 2.75;
+keeper_slant_slope = 2; // larger means steeper
+
+keeper_gap_z_top = 0.25;
+keeper_gap_z_bot = 0.75;
+keeper_gap_x     = 0.25;
+keeper_gap_x_holes = 0.75;
+keeper_fatter = 0.45;
+keeper_fatter_hole = 1.20;
+keeper_stubbier = 0.0;
+
+keeper_side = 0; // 0 = lhs; 1 = rhs
+
+case_lip = 1.25;
+
+lid_gap_x = 0.25;
+lid_gap_z = 0.25;
+lid_lip = 1.75;
+lid_edgepart_width = 5.0;
+lid_buttoncover_thick = 1.3;
+lid_buttoncover_reinf = 0.95;
+
+foldover_gap = 0.50;
+foldover_lever_gap = 0.50;
+
+// properties of the hinge fasteners
+hingescrew_shaft_dia = 1.600 + 0.45; // beading wire
+hingescrew_shaft_len = 10;
+hingescrew_fasteners_extra_thick = 0.40;
+// ^ amount of thread protruding if everything was completely nominal
+//   and we are using two nuts
+hingescrew_nut_access_dia = 4.72 + 0.50;
+// ^ washer is 4.72 dia
+//   also, want to get pliers or tiny spanner in to do up locknut
+hingescrew_nut_across = 3.92 + 0.25; // incl. slop around recess slop
+hingescrew_nut_thick = 1.93;
+hingescrew_head_th = 1.38 + 0.75;
+hingescrew_head_dia = 3.92;
+
+hingescrew_nut_recess_portion = 2/3; // portion of nut in recess
+
+lever_cover_th = 0.75;
+hingemount_th = 2.5;
+hingemount_wd = 4.8725;
+
+$fa = 5;
+$fs = 0.1;
+
+button_l_fudge = 4.4;
+buttonishleg_default_l_is_fudge = 10;
+
+hinge_base_slope = 1.5; // bigger is steeper
+
+strut_min_at_end = 1.5;
+
+hinge_x_gap = 0.125;
+hinge_x_postscrew_gap = 0.75;
+hinge_x_arms_gap = 0.35;
+hinge_r_arms_gap = 0.55;
+hinge_over_nut_plate = -0.50; // bodge apropos slope
+
+// there isn't one of these, speaker is by hinge
+// rearspeaker_gap    = [ 2.0, 2.0 ]; // each side
+
+thumbrecess_depth = 1.3;
+thumbrecess_width = 16.5;
+thumbrecess_topcurve_r = 5.0;
+
+prop_recess_under = 0.50;
+prop_recess_slop = 0.200; // each side
+prop_end_dia = 0.5;
+prop_main_th = 3;
+prop_taper_len = 6;
+prop_main_width = 4;
+prop_side_gap = 0.75; // each side
+prop_lidrecess_behind = 0.75;
+prop_caserecess_behind = 0.75;
+prop_caserecess_taper = 0.45; // one side only
+prop_prop_gap = 0.5;
+prop_prong_heel_slope = 0.5;
+
+lid_fold_clearance_antislop = 0.5;
+
+$button_leg_only = false;
+$suppress_forward_holes = false;
+$suppress_hinge = false;
+
+// ---------- calculated ----------
+
+phone_total_thick = phone_edge_thick;
+
+phone_width =  (phone + bumper*2)[0];
+phone_height = (phone + bumper*2)[1];
+
+inside_br = [phone_width, -phone_height];
+
+prop_prong_h = prop_main_th;
+
+//echo(camera_pos_tl + bumper,
+//     camera_pos_br + bumper);
+
+// ----- could be changed -----
+lid_buttoncover_gap = lid_gap_x;
+lid_buttoncover_overlap = case_th_lip + keeper_gap_z_top;
+
+//prop_lidrecess_depth = case_th_lid - prop_recess_under;
+
+//prop_nose_len = case_th_lid - prop_recess_under;
+//prop_recess_slope = tan(prop_max_angle); // bigger means steeper
+//prop_recess_width = prop_main_th / cos(prop_max_angle) + prop_backfwd_gap;
+
+
+epp0 = [0,0];
+epp1 = [0, -phone_edge_thick];
+epp2i = epp1; // conflated for FP4
+epp2o = epp2i;
+epp3 = epp2i + [10, 0];
+epp5 = epp0 + [0,1] * (keeper_th_z + keeper_gap_z_top + case_lip);
+epp4 = epp5 + [-1,0] * case_th_side;
+
+kppe = [0,0];
+kppd = kppe + [1,0] * keeper_inner_width;
+kppc = kppd + [0,1] * keeper_th_z;
+kppb = [ kppe[0] - keeper_th_x, kppc[1] ];
+kppf = kppe - [0,1] * keeper_inner_height;
+kppa = [ kppb[0], kppf[1] ];
+
+lpp10 = [ epp5[0] + lid_gap_x, kppc[1] + lid_gap_z ];
+lpp11 = [ lpp10[0],            epp5[1] + lid_gap_z ];
+
+lpp14 = lpp10 + [1,0] * max(keeper_inner_width, lid_edgepart_width);
+// exact x posn not very important; must extend past end of keeper
+
+lpp15 = [ lpp14[0],
+         epp0[1] - phone_rim_depth + 1/2.5 * case_th_lid
+         + lid_screen_gap_extra ];
+// ^ beam theory says to maximise force before contact,
+//   the gap below the `beam' (the lid) must be 1/3
+//   the thickness (ie the lid thickness) if the beam
+//   is solid, or 1/2 if it has a top and bottom only.
+//   ours is mostly solid.
+
+lp_r12 = max(case_th_lid - (lpp11[1] - lpp15[1]),
+            case_th_lip);
+
+lpp12 = [ epp4[0] + lp_r12,    lpp11[1] ];
+lpp13 = [ lpp12[0],            lpp12[1] + lp_r12 ];
+
+case_bottom_z = epp2o[1] - case_th_bottom;
+
+// button profile
+bppM = epp4 + [0,5];
+bppN = [ bppM[0] + lid_buttoncover_thick, bppM[1] ];
+bppR = [ bppN[0] + lid_buttoncover_gap, -button_cutout_depth ];
+bppS = [ epp1[0], bppR[1] ];
+bppQ = [ bppM[0], bppR[1] - lid_buttoncover_overlap ];
+bppP = bppQ + [0,1] * lid_buttoncover_gap;
+bppO = [ bppN[0], bppP[1] ];
+bppL = lpp10 + [5,0];
+bppK = [ bppL[0], bppN[1] ];
+bppJ = [ bppN[0], bppL[1] ];
+bppU = [ bppJ[0], lpp12[1] ];
+bppV = lpp11;
+bppW = lpp10;
+
+echo("BUTTON COVER TH", bppO[0] - bppP[0]);
+
+// notification led aperture
+
+nla_r0 = led_aperture/2;
+nla_r1 = nla_r0 + led_window_ledge;
+nla_r2 = nla_r1 + multicolour_gap;
+nla_t =
+  led_window_style >= 3 ? initial_layer_thick :
+  led_window_style >= 2 ? led_window_ledge : 0;
+
+
+// hinge plan
+hp_rn = hingescrew_nut_access_dia/2;
+hp_r2_min = hp_rn + lever_cover_th;
+hp_rs = hingescrew_shaft_dia/2;
+hp_r1_min = hp_rs + hingemount_th;
+
+hp_r1 = max(hp_r1_min, hp_r2_min);
+hp_r2 = hp_r1;
+
+hppU = lpp13;
+hppS = epp2o + [0,-1] * case_th_bottom;
+hp_k = 0.5 * (hppU[1] - hppS[1] + foldover_gap);
+
+hppM = [ epp4[0] - foldover_lever_gap - hp_r2,
+        0.5 * (hppU + hppS)[1] ];
+hppT = [ hppM[0], hppU[1] - hp_r1 ];
+hppB = hppT + [0,-1] * hp_k;
+
+hppE_y = epp2o[1] - case_th_bottom + hp_r1;
+hppE_x = hppB[0] + (hppB[1] - hppE_y) * hinge_base_slope;
+hppE = [ hppE_x, hppE_y ];
+
+// hinge elevation x coords
+
+hex20 = max(epp2o[0],
+           phone_cnr_rad,
+           kppd[0] + hingescrew_head_th + keeper_gap_x_holes);
+hex21 = hex20 + hingemount_wd;
+hex22 = hex21 + hinge_x_gap;
+hex27 = hex20 + hingescrew_shaft_len;
+hex24 = hex27 + hinge_x_postscrew_gap;
+hex23 = hex27 - (hingescrew_nut_thick*2
+                + hingescrew_fasteners_extra_thick);
+hex26 = hex23 + hingescrew_nut_thick * 2/3;
+
+//echo(hex20, hex21, hex22, hex23, hex24);
+////  6, 10.8725, 10.9975, 13.74, 18.75
+//module chk(act,exp) {
+//  if (abs(act-exp) > 1e-9) echo("WRONG", act, exp);
+//  else echo("ok", act);
+//}
+//chk(hex20, 6);
+//chk(hex21, 10.8725);
+//chk(hex22, 10.9975);
+//chk(hex23, 13.74);
+//chk(hex24, 18.75);
+
+lid_fold_clearance_skew =
+  (lpp10[1] - hppB[1]) /
+  (lpp10[0] - hppB[0]);
+
+echo("SK",lid_fold_clearance_skew);
+
+// thumb recess (used to be "catch" hence cpp*
+
+cppA = epp4 + [thumbrecess_depth, 0];
+cppB = [ cppA[0], epp1[1] ];
+
+// lanyard
+
+ly_r = lanyard_half_dia / 2;
+ly_rc = ly_r * 2;
+
+ly_theta = 90;
+ly_o = epp2i + 3 * ly_r * [0,1];
+
+max_case_bottom_edge_thickness =
+  case_th_bottom;
+
+ly_q_z = -(ly_rc + ly_r);
+ly_re = max_case_bottom_edge_thickness - (-ly_q_z);
+
+ly_oec_y = lanyard_entry_rel_breadth * ly_r;
+
+// prop recess in case
+
+prop_x_pos = phone_width/2;
+
+prop_recess_hw = 0.5 * prop_main_width + prop_side_gap;
+
+prc_r1 = prop_end_dia/2;
+prc_r3 = prc_r1 + prop_recess_slop;
+
+prcp2 = [ epp4[0] + prop_buildout_less,
+         case_bottom_z ];
+
+prop_caserecess_buildout_r = -1; // prcp2[0] - epp2o[0];
+
+prcp1 = [ epp2o[0] + prc_r3 + prop_caserecess_behind,
+         epp2i[1] - prc_r3 - prop_recess_under];
+
+// prop recess in lid
+
+prl_r10 = prop_end_dia/2;
+prl_r10o = prl_r10 + prop_recess_slop;
+
+prlp10 = lpp10 + [1,1] * prl_r10o
+  + [1,0] * prop_lidrecess_behind
+  + [0,1] * prop_recess_under;
+
+// prop
+
+$prpp10 = [0,0];
+$prpp11 = [0, prop_taper_len];
+
+$prp_r10 = prl_r10;
+
+// ---------- modules ----------
+
+module AdhocMultiprintFrame(phase, z0, zs) {
+  // from z0 to z0 + zs*layer
+  extra = phase * (initial_layer_width + multicolour_gap) + 5;
+  xextra = extra + -epp4[0];
+  xrange = [ 0, phone_width ] + [-1,+1] * xextra;
+  yextra = extra + -epp4[0];
+  yrange = [ -phone_height + +hppB[0] - hp_r2, 0 ] + [-1,+1] * yextra;
+  p0 = [ xrange[0], yrange[0] ];
+  p1 = [ xrange[1], yrange[1] ];
+  echo(p0, p1);
+  translate([0,0, z0])
+    mirror([0,0, zs<0 ? 1 : 0])
+    linear_extrude(height= initial_layer_thick)
+    difference(){
+      rectfromto(p0 - [1,1] * initial_layer_width,
+                p1 + [1,1] * initial_layer_width);
+      rectfromto(p0, p1);
+    }
+}
+
+module KeeperProfile(fatter=0, slant=0, stubbier=0){
+  use_e = kppe + [0,-1] * slant * keeper_inner_width / keeper_slant_slope;
+  polygon([use_e + [+1,-1] * fatter,
+          kppd  + [ 0,-1] * fatter - stubbier * [1,0],
+          kppc                     - stubbier * [1,0],
+          kppb,
+          kppa                     + stubbier * [0,1],
+          kppf  + [+1, 0] * fatter + stubbier * [0,1]
+          ]);
+}
+
+module EdgeProfile(){
+  difference(){
+    hull(){
+      translate(epp3) square(case_th_bottom*2, center=true);
+      circleat(epp2o, r=case_th_bottom);
+      circleat(epp1, r=case_th_side);
+      rectfromto(epp0, epp4);
+    }
+    polygon([ epp5 + [0,10],
+             epp1,
+             epp3 + [10,0] ]);
+  }
+}
+
+module LanyardLanyardProfile(entry=false){
+  hull(){
+    for (xs=[-1,+1] * (entry ? lanyard_entry_rel_breadth : 1))
+      translate(xs * 0.5 * lanyard_half_dia * [1,0])
+       circle(r= lanyard_half_dia/2);
+  }
+}
+
+module LanyardCurveChannelProfile(){
+  translate([0, -ly_r])
+    LanyardLanyardProfile();
+}  
+
+module LanyardEntryChannelProfile(){
+  LanyardLanyardProfile(true);
+}  
+
+module LanyardMainChannelProfile(){
+  LanyardCurveChannelProfile();
+  difference(){
+    square(center=true, ly_r * [6, 2]);
+    for (xs=[-1,+1])
+      translate(ly_r * [3 * xs, -1])
+       circle(r = ly_r);
+  }
+}
+
+module LanyardEntryOuterProfile(){
+  circleat([ly_re + ly_r, 0], ly_re);
+}
+
+module LanyardEntry(){
+  q_z = ly_q_z;
+  oec_y = ly_oec_y;
+
+  d_x = -ly_rc;
+
+  translate([d_x, 0, q_z]) {
+    intersection(){
+      rotate([90,0,0])
+       rotate_extrude(convexity=10)
+       rotate(90)
+       translate([0, -q_z])
+       LanyardCurveChannelProfile();
+      translate([0,-10,0])
+       cube([20,20,20]);
+    }
+  }
+
+  mirror([0,0,1])
+    translate([0,0,-1])
+    linear_extrude(height=20)
+    rotate(-90)
+    LanyardEntryChannelProfile();
+
+  translate([0, ly_r*2, 0])
+    rotate([90,0,0])
+    linear_extrude(height = ly_r*4){
+    difference(){
+      rectfromto([d_x, q_z], [ly_r, 0]);
+      circleat([d_x, q_z], ly_rc);
+    }
+  }
+
+  translate([0,0,q_z]){
+    for (my=[0,1])
+      mirror([0,my,0]){
+       translate([0, oec_y, 0]){
+         difference(){
+           translate(ly_re * [-1,0,-2])
+             cube(ly_re * [2,1,2]);
+           rotate_extrude(convexity=10)
+             LanyardEntryOuterProfile();
+         }
+       }
+      }
+    difference(){
+      translate([-ly_re, -(oec_y + 0.01), -2*ly_re])
+       cube([ly_re*2, 2*(oec_y + 0.01), 2*ly_re]);
+      for (mx=[0,1])
+       mirror([mx,0,0])
+         rotate([90,0,0])
+         translate([0,0,-10])
+         linear_extrude(height=20)
+         LanyardEntryOuterProfile();
+    }
+  }
+}
+
+module LanyardCutout(l){
+  rotate([0,-90,0])
+    linear_extrude(height=l)
+    rotate(-90)
+    LanyardMainChannelProfile();
+
+  for (ee=[0,1]){
+    translate(ee * l * [-1,0])
+      mirror([ee,0,0])
+      LanyardEntry();
+  }
+}
+
+module LidEdgeProfile(){
+  polygon([ lpp10,
+           lpp11,
+           lpp12,
+           lpp13,
+           lpp13 + [10, 0],
+           lpp15 + [10, 0],
+           lpp15,
+           lpp14,
+           ]);
+  intersection(){
+    circleat(lpp12, r=lp_r12);
+    rectfromto( lpp12 + [-10,   0],
+               lpp12 + [+10, +10] );
+  }
+}
+
+module LidEdgeFoldClearanceProfile(){
+  translate([-lid_fold_clearance_antislop, 0])
+    polygon([ lpp10,
+             lpp11,
+             lpp11 + [-20,  0],
+             lpp11 + [-20, 20],
+             lpp11 + [+20, 20],
+             lpp10 + [+20,  0] ]);
+}
+
+module ButtonCoverProfile(){
+  intersection(){
+    polygon(concat([ bppM, bppP, bppO, bppJ ],
+                  (enable_support && !$button_suppress_over_keeper
+                   ? [ bppU, bppV, bppW ] : []),
+                  [ bppL, bppK ]));
+    hull(){
+      EdgeProfile();
+      LidEdgeProfile();
+    }
+  }
+}
+
+module ButtonPlan(l, deep, cut){
+  epsilon =
+    (cut  ? 0 : lid_buttoncover_gap);
+
+  delta =
+    (deep ? lid_buttoncover_overlap : 0);
+
+  C = [0,0]; // by definition
+  T = [ 0, epp4[1] ];
+  G = T + [0,10];
+
+  B0 = C + [0,-1] * button_cutout_depth;
+  B1 = B0 + [0,1] * epsilon;
+
+  r0 = 0.5 * (T[1] - B0[1]);
+  A = [  -(l + button_l_fudge)/2 + r0, 0.5 * (T[1] + B0[1]) ];
+  H = A + [0,-1] * delta;
+
+  D = A + [-2,0] * r0;
+  F = D + [0,10];
+
+  E0 = 0.5 * (D + A);
+  E1 = E0 + [1,0] * epsilon;
+
+  I0 = [ E0[0], H[1] ];
+  I1 = [ E1[0], H[1] ];
+
+  hull(){
+    for (m=[0,1]) mirror([m,0])
+      circleat(H, r0 - epsilon);
+  }
+  for (m=[0,1]) mirror([m,0]) {
+    difference(){
+      polygon([ E1,
+               I1,
+               H,
+               B1,
+               G,
+               F,
+               D
+               ]);
+      circleat(D, r0 + epsilon);
+    }
+  }
+}
+
+module ButtonCoverReinf(){ ////toplevel
+  minkowski(){
+    rotate([90,0,0])
+      linear_extrude(height=0.01)
+      intersection(){
+        ButtonCoverProfile();
+       translate([bppJ[0] + 0.1, -50]) mirror([1,0])
+         square([100,100]);
+    }
+    mirror([0,0,1]) linear_extrude(height=0.01) intersection(){
+      circle(r= lid_buttoncover_reinf);
+      translate([-20,0]) square(40, center=true);
+    }
+  }
+}
+
+module ThumbRecessCutProfile(){
+  difference(){
+    polygon([ cppA + [-10,0],
+             cppB + [-10,0],
+             cppB,
+             cppA ]);
+    circleat(epp1, r=case_th_side);
+  }
+}
+
+module Flip_rhs(yn=[0,1]) {
+  for ($rhsflip=yn) {
+    translate([phone_width/2, 0, 0])
+      mirror([$rhsflip,0,0])
+      translate([-phone_width/2, 0, 0])
+      children();
+  }
+}
+
+module Flip_bot(yn=[0,1]) {
+  for ($botflip=yn) {
+    translate([0, -phone_height/2, 0])
+      mirror([0, $botflip, 0])
+      translate([0, phone_height/2, 0])
+      children();
+  }
+}  
+
+module AroundEdges(fill_zstart, fill_th, fill_downwards=0){
+  // sides
+  Flip_rhs(){
+    translate([0, -phone_cnr_rad, 0])
+      rotate([90,0,0])
+      linear_extrude(height = phone_height - phone_cnr_rad*2)
+      children(0);
+  }
+  // corners
+  Flip_rhs() Flip_bot() {
+    translate([+1,-1] * phone_cnr_rad)
+      intersection(){
+       rotate_extrude()
+         intersection(){
+           mirror([1,0,0])
+             translate([-1,0] * phone_cnr_rad)
+             children(0);
+           rectfromto([0,-20],[10,20]);
+         }
+       translate([-10, 0, -20] + 0.01 * [+1,-1, 0] )
+         cube([10,10,40]);
+      }
+  }
+  // top and bottom
+  Flip_bot(){
+    translate([ phone_width - phone_cnr_rad, 0,0 ])
+      rotate([90,0,-90])
+      linear_extrude(height = phone_width - phone_cnr_rad*2)
+      children(0);
+  }
+  // fill
+  translate([0,0, fill_zstart])
+    mirror([0,0, fill_downwards])
+    linear_extrude(height = fill_th)
+    rectfromto([+1,-1] * phone_cnr_rad,
+              [phone_width, -phone_height] + [-1,+1] * phone_cnr_rad);
+}
+
+module CaseAperture(pos, dia, $fn, topbottom=0) {
+  theta = 180/$fn;
+  translate([ bumper[0],
+             -epp2i[0],
+              0 ])
+    rotate([0,0, 90*topbottom])
+    translate([ pos[0] * (topbottom>0 ? -1 : +1), 0, -pos[1] ])
+    rotate([-90, theta, 0])
+    cylinder(r = dia/2 / cos(theta),
+            h = 60);
+}
+
+module SideButton(y, y_ref_sign, l, suppress_over_keeper=0){
+  // y_ref_sign:
+  //   +1  measured from top    of actual phone to top    of button
+  //   -1  measured from bottom of actual phone to bottom of button
+  //    0  y is centre of button in coordinate system
+  $button_l= l;
+  $button_suppress_over_keeper= suppress_over_keeper;
+  eff_y = y_ref_sign > 0 ?         -bumper [1] -y -l/2 :
+         y_ref_sign < 0 ? (-phone -bumper)[1] +y +l/2 :
+         y;
+  //echo(eff_y);
+  translate([0, eff_y, 0])
+    children();
+}
+
+module LidButtonishLeg(y, y_ref_sign, l=buttonishleg_default_l_is_fudge) {
+  $button_leg_only = true;
+  SideButton(y, y_ref_sign, l) children();
+}
+
+module Buttons(){
+  Flip_rhs(1) SideButton(30.320, +1, 22.960  ) children(); // volume
+  Flip_rhs(1) SideButton(64.220, +1, 14.500  ) children(); // power
+  Flip_rhs(1) LidButtonishLeg(14, -1) children();
+  Flip_rhs(0) LidButtonishLeg(21, -1) children();
+  Flip_rhs(0) LidButtonishLeg(38, +1) children();
+  Flip_rhs(0) LidButtonishLeg(14, +1) children();
+}
+
+module Struts(x_start, z_min, th){
+  // if th is negative, starts at z_min and works towards -ve z
+  // and object should then be printed other way up
+  for (i= [1 : 1 : case_struts_count]) {
+    translate([0,
+              0,
+              z_min])
+      mirror([0,0, th<0 ? 1 : 0])
+      translate([0,
+                -phone_height * i / (case_struts_count+1),
+                case_struts_solid_below])
+      linear_extrude(height= abs(th)
+                    -(case_struts_solid_below+case_struts_solid_above))
+      rectfromto([               x_start, -0.5 * case_struts_width ],
+                [ phone_width - x_start, +0.5 * case_struts_width ]);
+  }
+}
+
+module OrdinaryRearAperture(rhs,bot, pos){
+  Flip_rhs(rhs) Flip_bot(bot)
+    linextr(-20, 20)
+    mirror([0,1])
+    translate(pos + bumper)
+    children();
+}
+
+module MicroUSBEtc(){
+  Flip_bot(1){
+    rotate([90,0,0])
+      mirror([0,0,1])
+      linextr(-epp2i[0], 60)
+      translate([0.5 * phone_width, 0, 0])
+      rectfromto([-microusb_width/2, epp2i[1] + microusb_below],
+                [+microusb_width/2, epp0[1] + -microusb_above]);
+  }
+}
+
+module OrdinaryBottomEdgeApertures(){
+  Flip_bot(1)
+    CaseAperture(mainmic_pos, mainmic_dia, 8);
+
+  Flip_bot(1) Flip_rhs(1) {
+    linextr_y_xz(-epp2i[0], 60)
+      hull()
+      for (x= [-1,+1]) {
+       translate([ -bottomspeaker_pos[0], -bottomspeaker_pos[1] ] +
+                 [ 0.5 * x * bottomspeaker_size[0] - bottomspeaker_size[1],
+                   0 ])
+         rotate(360/16)
+         circle(r = bottomspeaker_size[1], $fn = 8);
+      }
+  }
+}
+
+module OrdinaryRearApertures(){
+  // rear speaker
+  //  OrdinaryRearAperture(1,1, rearspeaker_pos_bl)
+  //    rectfromto(-rearspeaker_gap,
+  //          rearspeaker_size + rearspeaker_gap);
+}
+
+module NotInTestFrameRearApertures(){
+  // finger hole to remove phone
+  if (len(fingerpushhole_dias))
+    OrdinaryRearAperture(0,0, [ fingerpushhole_dias[0] + epp2i[0],
+                               phone[1]/2 ])
+    scale(fingerpushhole_dias)
+    circle(r= 0.5 );
+}
+
+module RearCameraAperture(){
+  Flip_rhs(1)
+    mirror([0, 0, 1])
+    translate([0,0,0])
+    hull() // there is some kind of bug if hull() is done in 2D here!
+    linear_extrude(height = 20)
+    mirror([0, 1, 0])
+    translate(bumper)
+    translate(camera_pos_tl)
+    for (xy = [ [0,0], [0,1], [1,0] ]) {
+      translate(
+         camera_edge_rad * [1,1] +
+         xy * (camera_sz - camera_edge_rad * 2)
+               )
+       circle(r = camera_edge_rad);
+    }
+}
+
+module HingeLidProfile(){
+  hull(){
+    circleat(hppT, hp_r1);
+    circleat(lpp12, lp_r12);
+    polygon([lpp10,
+            lpp13 + [2,0],
+            lpp12,
+            hppT]);
+  }
+}
+
+module HingeBaseProfile(){
+  difference(){
+    hull(){
+      circleat(hppB, hp_r1);
+      circleat(hppE, hp_r1);
+      circleat(epp2o, case_th_bottom);
+      circleat(hppB + [10,0], hp_r1);
+    }
+    polygon([epp5, epp1, epp3, bppL]);
+  }
+}
+
+module HingeLeverOuterProfile(){
+  hull(){
+    circleat(hppT, hp_r2);
+    circleat(hppB, hp_r2);
+  }
+}
+
+module HingeLeverInnerProfile(){
+  for (s = [-1,+1]) {
+    c = s > 0 ? hppT : hppB;
+    translate(c)
+      mirror([0,0, s>0 ? 1 : 0])
+      rotate(s<0 ? -40 : 0)
+      hull()
+      for (x=[-20,20])
+       for (y=[0, s * 10])
+         translate([x,y])
+           circle(hp_rn);
+  }
+}
+
+module HingeLeverNutProfile(){
+  for (c= [hppB, hppT]) {
+    translate(c)
+      circle($fn=6, r= 0.5 * hingescrew_nut_across / cos(30));
+  }
+}
+
+module Flip_hinge(doflip=1){
+  hinge_origin = [0, -(phone_height - hppB[0]), hppB[1]];
+  translate(hinge_origin)
+    rotate([doflip*180,0,0])
+    translate(-hinge_origin)
+    children();
+}
+
+module HingePortion(x0,x1){
+  Flip_rhs() Flip_bot(1)
+    translate([x0,0,0])
+    mirror([1,0,0])
+    rotate([90,0,-90])
+    linear_extrude(height=x1-x0)
+    children();
+}
+
+module ThumbRecessApply(ztop){
+  width = thumbrecess_width;
+  w = width + thumbrecess_topcurve_r*2 + 1;
+  translate([phone_width/2, 0,0]){
+    difference(){
+      rotate([90,0,-90])
+       linextr(-w/2, w/2)
+       children(0);
+      translate([0, 50, 0])
+       rotate([90,0,0])
+       linear_extrude(height=100){
+       for (m=[0,1]) mirror([m,0,0]) {
+         hull(){
+           translate([w/2, ztop - thumbrecess_topcurve_r])
+             circle(thumbrecess_topcurve_r);
+           translate([w/2, -50])
+             square(thumbrecess_topcurve_r*2, center=true);
+         }
+       }
+      }
+    }
+  }
+}
+
+module CaseBase(){
+  AroundEdges(epp3[1], case_th_bottom, 1)
+    EdgeProfile();
+}
+
+function prop_x(gamma) = hp_k / (2 * sin(gamma/2)) - hppT[0];
+
+module PropProfileAssignments(gamma){
+  // https://en.wikipedia.org/wiki/Solution_of_triangles#Two_sides_and_the_included_angle_given_(SAS)
+  x = prop_x(gamma);
+  p = phone_height + prlp10[0] - hppB[0];
+  b = p + x;
+
+  q = phone_height - hppT[0] - prcp1[0]; // $prpp7[0] is 0 by definition
+  a = q + x;
+  c = sqrt(a*a + b*b - 2*a*b*cos(gamma));
+  $prp_alpha = acos( (b*b + c*c - a*a) / (2*b*c) );
+
+  $prp_theta = 90 - $prp_alpha;
+  beta = 180 - $prp_alpha - gamma;
+  psi = 90 - beta;
+
+  //echo("abc", a,b,c);
+
+  v1 = [ [ cos(psi), -sin(psi) ],    // x
+        [ sin(psi),  cos(psi) ] ];  // y
+
+  $prpp7 = [0, c + (lpp13[1] - $prpp10[1] - hp_k) ];
+
+  $prp_r1 = prc_r1;
+  $prp_r11 = prop_main_th/2;
+
+  $prpp1 = $prpp7 + [1,0] *
+    // this is approximate, but will do
+    (prop_main_th/2 + prop_prop_gap + prcp1[0] - cppA[0]);
+  $prpp3 = $prpp1 +
+    v1[0] * -$prp_r1 +
+    v1[1] * ((prcp2[1] - prcp1[1]) - prop_prop_gap);
+  $prpp12 = $prpp3 + v1[0] *
+    (prop_end_dia + prop_caserecess_taper * ($prpp1[1] - $prpp3[1]));
+  $prp_r8 = prop_main_th;
+  $prpp4 = [ prop_main_th/2, $prpp3[1] ];
+  $prp_r5 = $prp_r8;
+  $prpp5 = [ $prpp12[0] - $prp_r5,
+           $prpp3[1] - prop_prong_h + $prp_r5 ];
+  $prpp6 = $prpp4 + [0,-1] * (prop_prong_h +
+         prop_prong_heel_slope * ($prpp5[0] - $prpp4[0]));
+  $prpp8 = $prpp4 + [0,-1] * $prp_r8;
+  $prpp9 = $prpp8 + [-1,0] * $prp_r8;
+
+  children();
+}
+
+module PropProfile(gamma, cut=0, rot=0){
+  PropProfileAssignments(gamma){
+
+    //#circleat($prpp3,1);
+    //#circleat($prpp12,1);
+
+    if (!cut) {
+      hull(){
+       translate($prpp8)
+         intersection(){
+           circle($prp_r8);
+           polygon([[-20,-0], [20,20], [0,0]]);
+         }
+       rectfromto($prpp6, $prpp9);
+       translate($prpp5) intersection(){
+         circle($prp_r5);
+         polygon([[-10,-10], [0,0], [10,0]]);
+       }
+       rectfromto($prpp12 + [0,-0.1], $prpp3);
+      }
+      hull(){
+       circleat($prpp1, $prp_r1);
+       rectfromto($prpp12 + [0,-0.1], $prpp3);
+      }
+    }
+    // main shaft
+    rotate([0,0, rot*-$prp_theta]){
+      hull(){
+       extra = cut ? prop_recess_slop : 0;
+       rectfromto($prpp6, $prpp9);
+       circleat($prpp11, $prp_r11 + extra);
+       circleat($prpp10, $prp_r10 + extra);
+      }
+    }
+  }
+}
+
+module PropAggregateProfile(){
+  for (angle = prop_angles)
+    PropProfile(angle, 0,0);
+}
+
+module Prop(){ ////toplevel
+  hw = prop_main_width/2;
+  linextr(-hw, +hw)
+    PropAggregateProfile();
+}
+
+module Case(){ ////toplevel
+  difference(){
+    union(){
+      CaseBase();
+
+      // ledge (fixed keeper)
+      Flip_rhs(1-keeper_side) intersection(){
+       rotate([90, 0, 0])
+         linear_extrude(height = phone_height + phone_cnr_rad * 2)
+         KeeperProfile(fatter=0, slant=1);
+
+       // outline of the whole case, to stop it protruding
+       translate([0,0, -25])
+         linear_extrude(height = 50)
+         hull()
+         Flip_bot()
+         circleat([+1,-1] * phone_cnr_rad, phone_cnr_rad + case_th_side/2);
+      }
+
+      // hinge
+      if (!$suppress_hinge)
+       HingePortion(hex20, hex21) HingeBaseProfile();
+
+      // buildout for prop recess
+      if (prop_caserecess_buildout_r > 0) Flip_rhs(1)
+       linextr(case_bottom_z, epp2i[1])
+       hull() {
+         for (dxs = [-1,+1])
+           circleat([ prop_x_pos + dxs * prop_caserecess_buildout_r,
+                      -epp2o[0] ],
+                    r = epp2o[0] - prcp2[0]);
+        }
+    }
+
+    // slot for keeper
+    Flip_rhs(keeper_side)
+      translate([0, -phone_cnr_rad, 0])
+      rotate([90, 0, 0])
+      linear_extrude(height = phone_height + phone_cnr_rad * 2)
+      minkowski(){
+        KeeperProfile(fatter=keeper_fatter_hole);
+       rectfromto([ -keeper_gap_x,    -keeper_gap_z_bot ],
+                  [ keeper_gap_x_holes,    +keeper_gap_z_top ]);
+      }
+
+    // front camera
+    RearCameraAperture();
+
+    // struts (invisible, because they're buried in the case)
+    Struts(epp2i[0], epp2i[1] - case_th_bottom, case_th_bottom);
+
+    Buttons(){
+      mirror([1,0,0])
+       rotate([90,0,90]) {
+         if (!($button_leg_only && enable_support))
+         intersection(){
+           translate([0,0,-10])
+             linear_extrude(height= 20)
+             ButtonPlan($button_l, 0,1);
+           if ($button_leg_only)
+             rotate([-90,90,0])
+               translate([phone_width/2, -400, kppe[1]])
+               mirror([1-abs($rhsflip - keeper_side),0,0])
+               cube([400, 800, 50]);
+           if (enable_support && !$button_suppress_over_keeper)
+             rotate([-90,90,0])
+             translate([-400, -400, kppd[1]])
+               mirror([0,0,1])
+               cube([800,800,100]);
+         }
+         translate([0,0, -bppR[0]])
+           linear_extrude(height= 20)
+           ButtonPlan($button_l, 1,1);
+        }
+      
+    }
+
+    // apertures along top edge
+    if (!$suppress_forward_holes) {
+      // CaseAperture(jack_pos, jack_dia, 8);
+      Flip_rhs(1)
+       CaseAperture(noisecancelmic_pos, noisecancelmic_dia, 8);
+    }
+    CaseAperture(lhshole_pos, noisecancelmic_dia, 8, 1);
+
+    OrdinaryBottomEdgeApertures();
+
+    OrdinaryRearApertures();
+    NotInTestFrameRearApertures();
+
+    MicroUSBEtc();
+
+    // gaps for the lid's hinge arms
+    if (!$suppress_hinge) {
+      HingePortion(hex20 - hinge_x_arms_gap,
+                  hex21 + hinge_x_arms_gap)
+       minkowski(){
+        HingeLidProfile();
+       circle(r= hinge_r_arms_gap, $fn= 8);
+      }
+
+      // screw holes in the hinge arms
+      HingeScrews();
+    }
+
+    // thumb recess
+    ThumbRecessApply(epp4[1])
+      ThumbRecessCutProfile();
+
+    // lanyard
+    Flip_bot(1)
+      translate([ly_o[0], -(phone_cnr_rad + ly_re), ly_o[1]])
+      rotate([0, ly_theta, 0])
+      rotate([0,0,90])
+      LanyardCutout(lanyard_channel_len);
+
+    // prop recess
+    Flip_rhs(1)
+      translate([prop_x_pos,0,0])
+      mirror([0,1,0])
+      rotate([90,0,90])
+      linextr(-prop_recess_hw, +prop_recess_hw)
+      hull(){
+        for (d=[ [0,0], [0,-1], [+1,-1/prop_caserecess_taper] ])
+         circleat(prcp1 + 20*d,
+                  prc_r3);
+      }
+  }
+}
+
+module LidAdhocMultiprintFrame(phase){
+  if (led_window_style >= 3) {
+    AdhocMultiprintFrame(phase, lpp13[1], -1);
+  }
+}
+
+module LidAroundEdges(){
+  AroundEdges(lpp15[1], lpp13[1] - lpp15[1], 0)
+    children();
+}
+
+module Lid(){ ////toplevel
+  skew_centre = [0, lpp11[0], lpp11[1]];
+  difference(){
+    union(){
+      intersection(){
+       LidAroundEdges()
+         LidEdgeProfile();
+
+       translate(skew_centre)
+         multmatrix([[ 1, 0, 0, 0 ],
+                     [ 0, 1, -lid_fold_clearance_skew, 0 ],
+                     [ 0, 0, 1, 0 ],
+                     [ 0, 0, 0, 1 ]])
+         translate(-skew_centre)
+         LidAroundEdges()
+         LidEdgeFoldClearanceProfile();
+      }
+
+      // button covers
+      Buttons(){
+       intersection(){
+         rotate([90,0,90])
+           translate([0,0,-10])
+           linear_extrude(height= 20)
+           ButtonPlan($button_l, 1,0);
+         union(){
+           rotate([90,0,0])
+             translate([0,0,-100])
+             linear_extrude(height= 200)
+             ButtonCoverProfile();
+           hull()
+             for (y= [-1,+1] * (($button_l + button_l_fudge)/2
+                                - lid_buttoncover_reinf))
+               translate([0,y,0])
+                 ButtonCoverReinf();
+         }
+       }
+      }
+
+      // hinge arms
+      HingePortion(hex20, hex21) {
+       LidEdgeProfile();
+       HingeLidProfile();
+      }
+    }
+    Struts(lpp10[0] + strut_min_at_end, lpp13[1], -case_th_lid);
+
+    // screw holes in the hinge arms
+    HingeScrews();
+
+    // prop recess
+    translate([prop_x_pos, -prlp10[0], prlp10[1]])
+      mirror([0,1,0])
+      rotate([90,0,90])
+      linextr(-prop_recess_hw, +prop_recess_hw)
+      hull()
+      for (pa = prop_angles)
+       PropProfile(pa, 1,1);
+
+    // notification led aperture
+    if (led_window_style)
+      translate([led_pos[0], -led_pos[1], lpp13[1]]) {
+       translate([0,0,-10])
+         cylinder(r=nla_r0, h=20);
+       if (led_window_style >= 2)
+         translate([0,0, -nla_t])
+           cylinder(r=nla_r2, height=20);
+      }
+
+    }
+
+  LidAdhocMultiprintFrame(1);
+}
+
+module HingeLever(){ ////toplevel
+  difference() {
+    // outer body, positive
+    HingePortion(hex22, hex22 + phone_width/2)
+      HingeLeverOuterProfile();
+
+    // space for the screws
+//    HingePortion(hex26, hex24)
+//      HingeLeverInnerProfile();
+
+    // recesses for the nuts
+//    HingePortion(hex23, hex26+1)
+//      HingeLeverNutProfile();
+
+    // bores for the screws
+    HingeScrews();
+
+    // space for the charging cable and speaker and micc apertures
+    hull() {
+      for (x = [-1,+1]) {
+       multmatrix([[ 1,0,
+
+                    x
+                    * ( (hex24 + hinge_over_nut_plate) -
+                        (phone_width/2 - microusb_width/2)
+                       )
+                    / ( (epp0[1] - microusb_above)
+                        -
+                        (hppB[1] - hp_r2) ),
+
+                    x * (epp0[1] - microusb_above)
+
+                     ],
+                   [ 0,1,0, 0 ],
+                   [ 0,0,1, 0 ]]) {
+         union(){
+           MicroUSBEtc();
+           Flip_hinge() MicroUSBEtc();
+         }
+       }
+      }
+    }
+  }
+}
+
+module LidWindow(){ ////toplevel
+  translate([led_pos[0], -led_pos[1], lpp13[1]])
+    mirror([0,0,1])
+    cylinder(r= nla_r1, h=nla_t);
+  LidAdhocMultiprintFrame(0);
+}
+
+module LidWindowPrint(){ ////toplevel
+  rotate([0,180,0])
+    LidWindow();
+}
+
+module DemoLidWindowSelect(){
+  translate([led_pos[0], led_pos[1], -100]) {
+    translate([0, -30, 0]) cube([400, 400, 200]);
+  }
+}
+
+module DemoLidWindow(){ ////toplevel
+  %Lid();
+  LidWindow();
+  translate([0,40,0]){
+    color("blue") intersection(){ Lid(); DemoLidWindowSelect(); }
+    color("red") intersection(){ LidWindow(); DemoLidWindowSelect(); }
+  }
+}
+
+module HingeLeverPrint(){ ////toplevel
+  rotate([-90,0,0])
+    translate([-phone_width/2, phone_height, 0])
+    HingeLever();
+}
+
+module TestSelectLength(){
+  translate([-30, -200, -20])
+    cube([30 + 15, 250, 40]);
+}
+
+module TestLength(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectLength();
+  }
+}
+
+module TestLengthRight(){ ////toplevel
+  intersection(){
+    Case();
+    Flip_rhs(1)
+      TestSelectLength();
+  }
+}
+
+module TestSelectWidth(){
+  translate([-30, -(phone_height - 25), -20])
+    mirror([0, 1, 0])
+    cube([200, 50, 40]);
+}
+
+module TestWidth(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectWidth();
+  }
+}
+
+module TestLidWidthPrint(){ ////toplevel
+  rotate([0,180.0]) intersection(){
+    Lid();
+    TestSelectWidth();
+  }
+}
+
+module TestSelectRearAperture(){
+  minkowski(){
+    union() children();
+    translate([20, 0,0])
+      cube([42, 2, 1], center=true);
+  }
+}
+
+module TestSelectCamera(){
+  minkowski(){
+    TestSelectRearAperture()
+      RearCameraAperture();
+    cube([0.1, 50, 0.1]);
+  }
+}
+
+module TestSelectOrdinaryRearApertures(){
+  TestSelectRearAperture()
+    OrdinaryRearApertures();
+}
+
+module TestCamera(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectCamera();
+  }
+}
+
+module TestLidByCamera(){ ////toplevel
+  intersection(){
+    Lid();
+    TestSelectCamera();
+  }
+}
+
+module TestLidByCameraPrint(){ ////toplevel
+  rotate([180,0,0]) TestLidByCamera();
+}
+
+module DemoByCamera(){ ////toplevel
+  color("blue") TestLidByCamera();
+  color("red")  TestCamera();
+}
+
+module OneKeeper(){ ////toplevel
+  translate([0, -phone_cnr_rad, 0])
+    rotate([90, 0, 0])
+    linear_extrude(height = phone_height - phone_cnr_rad * 2)
+    KeeperProfile(fatter=keeper_fatter, stubbier=keeper_stubbier);
+}
+
+module OneKeeperPrint(){ ////toplevel
+  rotate([0,180,0])
+    OneKeeper();
+}
+
+module LidPrint(){ ////toplevel
+  rotate([0,180,0])
+    Lid();
+}
+
+module TestSelectFrame(){
+  include = [1,-1] * (epp2i[0] + 4);
+
+  difference(){
+    cube(1000, center=true);
+    translate([0,0, -100])
+      linear_extrude(height=200)
+      rectfromto(include,  inside_br - include);
+  }
+
+  for (i= [1,2]) {
+    translate([ 0, -phone[1] * i/3, 0 ])
+      cube(center=true, [1000, 4, 100]);
+  }
+}
+
+module TestSelectLidFrame(){
+  TestSelectFrame();
+  if (len(led_pos))
+    translate([led_pos[0], -led_pos[1], -50])
+    cylinder(r= nla_r2+3, h=100);
+}
+
+module TestFrameCase(){ ////toplevel
+  intersection(){
+    Case();
+    union(){
+      TestSelectFrame();
+      TestSelectCamera();
+      TestSelectOrdinaryRearApertures();
+    }
+  }
+}
+
+module TestSelectTopApertures(){
+  translate([-100, -35, -100])
+    cube([400, 100, 200]);
+  LidAdhocMultiprintFrame(0);
+  LidAdhocMultiprintFrame(1);
+}
+
+module TestTopApertures(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectFrame();
+    TestSelectTopApertures();
+  }
+}
+
+module TestLidTopAperturesPrint(){ ////toplevel
+  rotate([0,180,0]) intersection(){
+    Lid();
+    TestSelectLidFrame();
+    TestSelectTopApertures();
+  }
+}
+
+module TestLidWindowTopAperturesPrint(){ ////toplevel
+  rotate([0,180,0]) intersection(){
+    LidWindow();
+    TestSelectTopApertures();
+  }
+}
+
+module TestFrameLidPrint(){ ////toplevel
+  rotate([0,180,0]) intersection(){
+    Lid();
+    TestSelectLidFrame();
+  }
+}
+
+module ButtonPlanForDemo(z, deep, cut){
+  translate([0,0,z])
+    ButtonPlan(8, deep, cut);
+}
+
+module HingeScrews(){
+  Flip_rhs() Flip_bot(1){
+    for (c= [ hppT, hppB ])
+      translate([ hex20,
+                 -c[0],
+                 c[1] ]){
+       rotate([0,90,0])
+         translate([0,0,-.2])
+         cylinder( r= hingescrew_shaft_dia/2,
+                   h = hingescrew_shaft_len+0.2 );
+       rotate([0,-90,0])
+         translate([0,0,+.1])
+         cylinder( r= hingescrew_head_dia/2, h = hingescrew_head_th );
+      }
+  }
+}
+
+module DemoPropAngleSelect(c){
+  color(c) difference(){
+    union(){ children(); }
+    translate([ prop_x_pos, -400, -200 ])
+      cube([ 400,800,400 ]);
+  }
+}
+
+module DemoPropAngle(ang){
+  hL = [0, -(phone_height - hppT[0]), hppT[1] - hp_k*2];
+  hC = [0, -(phone_height - hppB[0]), hppB[1]];
+
+  translate(hL)
+    rotate([ang/2,0,0])
+    translate(-hL)
+    translate(hC)
+    rotate([ang/2,0,0])
+    translate(-hC) {
+      DemoPropAngleSelect("red") Case();
+
+      color("orange")
+       translate([prop_x_pos, -prcp1[0], prcp1[1]])
+       PropProfileAssignments(ang) {
+          echo($prpp1);
+         rotate([-$prp_theta, 0, 0])
+         translate([0, $prpp1[0], -$prpp1[1]])
+         rotate([90,0,-90])
+         Prop();
+        }
+    }
+
+  translate([0,0, -hp_k*2])
+    DemoPropAngleSelect("blue")
+    Lid();
+}
+
+module DemoPropAngles(){ ////toplevel
+  for (i=[0 : len(prop_angles)-1])
+    translate(i * [0, -100, 100])
+    DemoPropAngle(prop_angles[i]);
+}
+
+module DemoHingeAngle(ang1,ang2){
+  hL = [0, -(phone_height - hppT[0]), hppT[1]];
+  hC = [0, -(phone_height - hppB[0]), hppB[1]];
+
+  translate(hL)
+    rotate([ang2,0,0])
+    translate(-hL)
+    translate(hC)
+    rotate([ang1,0,0])
+    translate(-hC) {
+      color("red") Lid();
+    }
+
+  color("blue") intersection(){
+    Case();
+    union(){
+      translate([bppJ[0], -400, -200])
+       mirror([1,0,0])
+       cube([400, 800, 400]);
+      translate([10, -400, -200])
+       cube([10, 800, 400]);
+    }
+  }
+}
+
+module DemoHingeAngles(){ ////toplevel
+  angles = [ 0, 4, 8, 12 ];
+  echo("angles",angles);
+  for (i=[0 : len(angles)-1]) {
+    translate(i * [0, 0, 30]) {
+      DemoHingeAngle(0,angles[i]);
+      translate([0, 200, 0])
+       DemoHingeAngle(angles[i],0);
+    }
+  }
+}
+
+module DemoSelectAdhocLeftRight(right=0) {
+  translate([phone_width/2, -400, -100]) // , -15, -100  to cross-section
+    mirror([1-right, 0,0])
+    cube([400, 800, 200]);
+}
+
+module DemoLeft(){ ////toplevel
+  color("red")  intersection(){ Case(); DemoSelectAdhocLeftRight(); }
+  color("blue") intersection(){ Lid();  DemoSelectAdhocLeftRight(); }
+}
+
+module DemoFrame(){ ////toplevel
+  color("red") render() TestFrameCase();
+  color("blue") render() intersection(){ Lid(); TestSelectLidFrame(); }
+  color("black") render() HingeScrews();
+  %render() HingeLever();
+}
+
+module DemoLanyardCutout(){ ////toplevel
+  LanyardCutout(25);
+}
+
+module DemoHingedFrame(){ ///toplevel
+  color("red") render() TestFrameCase();
+  translate([0,0, -2*hp_k])
+  color("blue") render() intersection(){ Lid(); TestSelectLidFrame(); }
+
+  Flip_hinge(){
+    color("orange") render() HingeLever();
+    color("black") render() HingeScrews();
+  }
+}
+
+module DemoHinge(){ ////toplevel
+  translate([ -0.5*phone_width, phone_height, hp_k*3 ]) {
+    DemoFrame();
+    translate([0,0, -hp_k*3])
+      DemoHingedFrame();
+  }
+}
+
+module DemoProfiles(){ ////toplevel
+  LidEdgeProfile();
+  %EdgeProfile();
+  KeeperProfile();
+  translate([0,0,-1]) color("black") KeeperProfile(1);
+  translate(ly_o){
+    rotate(-ly_theta){
+      translate([0,0,+1]) color("purple") LanyardMainChannelProfile();
+      translate([0,0,+2]) color("red") LanyardCurveChannelProfile();
+      translate([0, ly_q_z]){
+       translate([0,0,-1]) color("blue") LanyardEntryChannelProfile();
+       translate([ly_oec_y,0,-2]) color("black") LanyardEntryOuterProfile();
+      }
+    }
+  }
+  translate([0,0,-5]) color("white") translate(epp2i)
+    rotate(-ly_theta)
+    rectfromto([-15, 0],
+              [+15, -max_case_bottom_edge_thickness]);
+
+  translate([0,20]) {
+    LanyardMainChannelProfile();
+    translate([0,0,1]) color("purple") LanyardCurveChannelProfile();
+    translate([0,0,-1]) color("red") LanyardEntryChannelProfile();
+  }
+
+  translate([20,0]) {
+    LidEdgeProfile();
+    %EdgeProfile();
+
+    demopoint_QR = [ bppS[0], bppQ[1] - 0.1];
+  
+    color("blue") ButtonCoverProfile();
+    color("red") {
+      rectfromto(bppQ, demopoint_QR);
+      rectfromto(bppR, demopoint_QR);
+    }
+  }
+
+  translate([-20,0]) {
+    color("black") ButtonPlanForDemo(-2, 0,1);
+    color("red" )  ButtonPlanForDemo(-4, 1,1);
+    color("blue")  ButtonPlanForDemo(-6, 1,0);
+  }
+
+  translate([0, -30]) {
+    %LidEdgeProfile();
+    %EdgeProfile();
+    color("blue") HingeLidProfile();
+    color("red")  HingeBaseProfile();
+    color("black") translate([0,0,-2]) HingeLeverOuterProfile();
+  }
+
+  for (f=[0,1]) {
+    translate([-30, -60 + 30*f]) {
+      translate([0,0,-4]) EdgeProfile();
+      %translate([0,0,-10]) HingeBaseProfile();
+      translate([0,-2] * f * hp_k) {
+       translate([0,0,-4]) LidEdgeProfile();
+       %translate([0,0,-10]) %HingeLidProfile();
+      }
+      translate(+hppB) rotate([0,0,180*f]) translate(-hppB) {
+       translate([0,0,-2]) color("black") HingeLeverOuterProfile(); 
+       translate([0,0,0]) color("red") difference(){
+         HingeLeverOuterProfile();
+         HingeLeverInnerProfile();
+       }
+       translate([0,0,3]) color("yellow") HingeLeverNutProfile();
+      }
+    }
+  }
+
+  translate([20,-30]) {
+    %EdgeProfile();
+    %LidEdgeProfile();
+    //translate([0,0,1]) ThumbRecessCutProfile();
+    translate([0,0,+1]) color("red")
+      difference(){ EdgeProfile(); ThumbRecessCutProfile(); }
+  }
+
+  translate([40,-30]) {
+    difference(){
+      LidEdgeProfile();
+      translate(prlp10)
+       PropProfile(10, 1, 0);
+    }
+    translate(prlp10)
+      PropProfile(15, 0);
+  }
+  translate([60,-30]) {
+    PropAggregateProfile();
+  }
+}
+
+//EdgeProfile();
+//KeeperProfile();
+//CaseBase();
+//%Case();
+//Keeper();
+//LidEdgeProfile();
+//KeeperProfile();
+//DemoProfiles();
+//PropRecess();