X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=reprap-play.git;a=blobdiff_plain;f=fairphone-case.scad;h=d83c53474f4b548972d672539cecb5210bf28c8b;hp=b3621ed3a47375dc9714fb064b6c019723195a86;hb=1e5609f5812a3103813be78c322b383c8a13d35b;hpb=3a85b5b53756a57f317e4add71c132de5554dac0 diff --git a/fairphone-case.scad b/fairphone-case.scad index b3621ed..d83c534 100644 --- a/fairphone-case.scad +++ b/fairphone-case.scad @@ -1,14 +1,149 @@ // -*- 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. Decide about the notification LED aperture. See the variable +// led_window_style, below. The default here is "ad-hoc +// multi-colour", which can produces a translucent (clear-ish) +// window set into the lid, even on a single-nozzle printer. +// See "Ad-hoc multi-colour", below. +// +// 3. use "make" to generate the necessary files: +// +// make -j8 fairphone-case.auto.scads `for f in \ +// HingePrint \ +// LidWindowPrint \ +// LidPrint \ +// OneKeeperPrint \ +// Case \ +// ; do echo fairphone-case,$f.auto.stl; done` +// +// 4. Print them. Case and OneKeeperPrint should probably be +// the same colour. +// +// For Lid and LidWindowPrint, if you are doing ad-hoc +// multi-colour: +// i. Set up for clear filament +// ii. Print LidWindowPrint. Wait for it to finish. +// It won't take long. As soon as it finishes, tell +// your printer to warm up (so that in fact it does +// not cool down). +// iii. Leaving the output so far on the printbed, reload +// your printer with the main lid colour. +// iv. Print LidPrint. You can let this go unattended. +// +// 5. Assemble the hinge. You will need 4x M2 12mm machine screws +// and 8x M2 full nuts. +// +// Make sure you get the hinge the right way round. If you're not +// sure, run +// openscad fairphone-case,DemoHinge.auto.scad +// to see an assembly diagram. +// +// The nuts recess into the hinge. You will want very fine +// pliers. As you screw each screw in, add the second nut when +// the screw thread emerges from the first - this will be a +// locknut. Screw each screw to an appropriate tightness for the +// hinge stiffness. You want the lid-side hinge to be stiffer as +// that makes closing the case work better. +// +// When you have the stiffness right, tighten the locknuts onto +// each first nut. +// +// 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 +// +// Ad-hoc multi-colour +// +// This file is set up to let you make a translucent window using a +// single-extruder printer, using a "two print run" technique. This +// works well with our Lulzbot TAZ 5 and Aleph Objects' version of +// Cura. If you are using a different printer, you may need to +// adjust the parameters or try a different technique. In particular, +// initial_layer_thick +// set so that the window is one layer thick +// initial_layer_width +// set so that the slicer draws a rectangle around the whole +// object, rather than putting a "skirt" or anything inside +// +// If you have a dual-extruder printer, you can set led_window_style +// to 2 and do a single print of LidPrint and LidWindowPrint. +// +// Alternatively you can set it to 1 (just a hole) or 0 (no hole). +// +// Thanks to Clare Boothby for the ad-hoc multi-colour technique (and +// the parameters for our Lulzbot TAZ 5 and Aleph Objects's Cura). +// +// 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 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 +// . +// +// 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 +include phone = [ 75.0, 145.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 = 3; +// 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 = 6.0; +phone_rim_depth = 0.80; // includes allowance for a screen protector button_cutout_depth = 9; @@ -20,22 +155,36 @@ phone_backside_slope_outer = 1.0; // larger means shallower camera_pos_tl = [ 6.450, 12.750 ]; // measured from tl corner camera_pos_br = [ 22.300, 37.600 ]; // tl/br as seen from back -jack_pos = [ 13.92, 7.96 ]; -jack_dia = 9.1 + .5; // some jack I had lying around +jack_pos = [ 13.83, 8.485 ]; +jack_dia = 10.64 + .5; // some jack I had lying around + +led_pos = [ 13.98, 10.00 ]; +led_aperture = 9; +led_window_ledge = 0.75; // each side noisecancelmic_pos = [ 19.54, 7.37 ]; // from rhs -noisecancelmic_dia = 1.75; +noisecancelmic_dia = 4.00; -fingerpushhole_dias = [ 14, 17 ]; +//fingerpushhole_dias = [ 15, 18 ]; +fingerpushhole_dias = []; +lanyard_half_dia = 1.15; +lanyard_entry_rel_breadth = 2; +lanyard_channel_len = 15; rearspeaker_pos_bl = [ 12.64, 18.72 ]; rearspeaker_size = [ 3.76, 7.36 ]; +microusb_above = 3.27 - 0.25; +microusb_below = 0.0; +microusb_width = 16.12 + 1.25; + case_th_bottom = 2.5; -case_th_lid = 2.5; -case_th_side = 2; +case_th_lid = 3.0; +case_th_side = 2.3; 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; @@ -52,23 +201,40 @@ keeper_gap_z_bot = 0.75; keeper_gap_x = 0.25; keeper_gap_x_holes = 0.75; +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; + +catch_slop = 0.50; foldover_gap = 0.50; foldover_lever_gap = 0.50; +// properties of the hinge fasteners hingescrew_shaft_dia = 2.0 + 0.25; // M2 x 12mm machine screw hingescrew_shaft_len = 12; -hingescrew_nut_thick = 1.93 + 0.20; // incl. washer -hingescrew_nut_dia = 4.72 + 0.50; // washer, actually +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; @@ -76,6 +242,8 @@ $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; @@ -83,7 +251,35 @@ hinge_x_postscrew_gap = 0.75; hinge_x_arms_gap = 0.35; hinge_r_arms_gap = 0.55; -rearspeaker_gap = [ 1.0, 1.0 ]; // each side +rearspeaker_gap = [ 2.0, 2.0 ]; // each side + +catch_len = 7.5; +catch_width = 15; +catch_thickness = 1.3; +catch_side_gap = 0.75; // each side + +catch_depth = 1.25; +catch_height = 0.35; +catch_finger_height = 1.5; +catch_finger_depth = 2.5; +catch_catch_slope = 0.25; + +catch_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; // ---------- calculated ---------- @@ -92,6 +288,8 @@ 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); @@ -101,6 +299,13 @@ lid_buttoncover_overlap = case_th_lip + keeper_gap_z_top; phone_backside_slope_thick = phone_total_thick - phone_edge_thick; +//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; + + //lid_lip_overlap_width xxx bad name = ; //lid_lip_inner_slope = [ 5, 5 ]; // xxx @@ -122,14 +327,29 @@ kppa = [ kppb[0], kppf[1] ]; lpp10 = [ epp5[0] + lid_gap_x, kppc[1] + lid_gap_z ]; lpp11 = [ lpp10[0], epp5[1] + lid_gap_z ]; -lp_r12 = case_th_lid - (lpp11[1] - lpp10[1]); +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 = [ 0.5 * (epp0[0] + epp4[0]), bppM[1] ]; +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 ]; @@ -138,12 +358,30 @@ 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_dia/2; -hp_r2 = hp_rn + lever_cover_th; +hp_rn = hingescrew_nut_access_dia/2; +hp_r2_min = hp_rn + lever_cover_th; hp_rs = hingescrew_shaft_dia/2; -hp_r1 = hp_rs + hingemount_th; +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; @@ -151,26 +389,137 @@ 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.5 * [0,+1] * hp_k; -hppB = hppM + 0.5 * [0,-1] * hp_k; +hppT = [ hppM[0], hppU[1] - hp_r1 ]; +hppB = hppT + [0,-1] * hp_k; -// hinge elevation x coords +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 ]; -hingescrew_portion_len = - 0.5* (hingescrew_shaft_len - hingescrew_nut_thick - hinge_x_gap); +// hinge elevation x coords hex20 = max(epp2o[0], phone_cnr_rad, kppd[0] + hingescrew_head_th + keeper_gap_x_holes); -hex21 = hex20 + hingescrew_portion_len; -hex22 = hex21 - hinge_x_gap; -hex23 = hex22 + hingescrew_portion_len; -hex24 = hex20 + hingescrew_shaft_len + hinge_x_postscrew_gap; +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); -echo(hingescrew_portion_len); +// 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); + +// catch + +cppJ = [ epp4[0] + catch_thickness, lpp10[1] ]; +cppA = cppJ + [lid_gap_x, -lid_gap_z]; +cppB = [ epp0[0], cppA[1] ]; +cppP = [ epp4[0], cppJ[1] ]; + +cppS = cppJ + [0,-1] * catch_len; +cppD = [ cppA[0], cppS[1] + catch_slop ]; +cppC = [ cppB[0], cppD[1] ]; +cppT = cppS + [1, -catch_catch_slope] * catch_depth; +cppU = cppT + [0,-1] * catch_height; +cppV = [ cppS[0], cppU[1] - catch_depth ]; + +cppR = 0.5*(cppP + cppJ); + +cp_rQ = 0.5 * (cppJ[0] - cppP[0]); +cppQ = [ cppR[0], + cppV[1] - (catch_finger_height - cp_rQ) ]; +cppF = [ cppV[0] + catch_finger_depth, cppC[1] ]; + +// lanyard + +ly_r = lanyard_half_dia / 2; +ly_rc = ly_r * 2; + +ly_theta = -atan2vector(epp2i - epp1); +ly_o = epp2i + 3 * ly_r * unitvector2d(epp1 - epp2i); + +max_case_bottom_edge_thickness = + case_th_bottom + + sin(ly_theta) * (epp2i-epp2o)[0]; + +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(slant=0){ use_e = kppe + [0,-1] * slant * keeper_inner_width / keeper_slant_slope; polygon([use_e, kppd, kppc, kppb, kppa, kppf]); @@ -191,13 +540,117 @@ module EdgeProfile(){ } } +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], - lpp10 + [10, 0] + lpp15 + [10, 0], + lpp15, + lpp14, ]); intersection(){ circleat(lpp12, r=lp_r12); @@ -206,9 +659,22 @@ module LidEdgeProfile(){ } } +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([ bppM, bppP, bppO, bppJ, bppL, bppK ]); + polygon(concat([ bppM, bppP, bppO, bppJ ], + (enable_support && !$button_suppress_over_keeper + ? [ bppU, bppV, bppW ] : []), + [ bppL, bppK ])); hull(){ EdgeProfile(); LidEdgeProfile(); @@ -262,6 +728,31 @@ module ButtonPlan(l, deep, cut){ } } +module CatchCatchProfile(){ + hull(){ + for (c=[ cppR, cppQ ]) + circleat(c, cp_rQ); + } + hull(){ + circleat(lpp12, lp_r12); + circleat(lpp12 + [5,0], lp_r12); + rectfromto(cppP, cppP + [5,0.1]); + } + polygon([cppJ, cppS, cppT, cppU, cppV, cppQ, cppR]); +} + +module CatchCutProfile(){ + polygon([ cppB, + cppA, + cppD, + cppF, + cppF + [0,-10], + cppF + [-10,-10], + lpp12 + [-10,0], + lpp12 + [10,0] + ]); +} + module Flip_rhs(yn=[0,1]) { for ($rhsflip=yn) { translate([phone_width/2, 0, 0]) @@ -328,12 +819,13 @@ module CaseAperture(pos, dia, $fn) { h = 60); } -module SideButton(y, y_ref_sign, l){ +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; @@ -348,10 +840,10 @@ module LidButtonishLeg(y, y_ref_sign, l=buttonishleg_default_l_is_fudge) { } module Buttons(){ - Flip_rhs(1) SideButton(15.580, +1, 8.9) children(); // power - Flip_rhs(1) SideButton(48.700, -1, 8.920) children(); // camera - Flip_rhs(0) SideButton(30.800, +1, 21.96) children(); // volume - Flip_rhs( ) LidButtonishLeg(20, -1) children(); + Flip_rhs(1) SideButton(15.580, +1, 8.9 ) children(); // power + Flip_rhs(1) SideButton(48.700, -1, 8.920 ) children(); // camera + Flip_rhs(0) SideButton(30.800, +1, 21.96, 1) children(); // volume + Flip_rhs( ) LidButtonishLeg(14, -1) children(); // Flip_rhs(0) LidButtonishLeg(20, +1, 20) children(); } @@ -381,6 +873,17 @@ module OrdinaryRearAperture(rhs,bot, pos){ children(); } +module MicroUSB(){ + 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 OrdinaryRearApertures(){ // rear speaker OrdinaryRearAperture(1,1, rearspeaker_pos_bl) @@ -388,8 +891,9 @@ module OrdinaryRearApertures(){ rearspeaker_size + rearspeaker_gap); // finger hole to remove phone - OrdinaryRearAperture(1,0, [ fingerpushhole_dias[0]/2 + epp2i[0], - phone[1]/2 ]) + if (len(fingerpushhole_dias)) + OrdinaryRearAperture(1,0, [ fingerpushhole_dias[0]/2 + epp2i[0], + phone[1]/2 ]) scale(fingerpushhole_dias) circle(r= 0.5 ); } @@ -418,6 +922,7 @@ module HingeBaseProfile(){ difference(){ hull(){ circleat(hppB, hp_r1); + circleat(hppE, hp_r1); circleat(epp2o, case_th_bottom); circleat(hppB + [10,0], hp_r1); } @@ -433,22 +938,65 @@ module HingeLeverOuterProfile(){ } module HingeLeverInnerProfile(){ - for (c = [hppT, hppB]) { - hull() - for (x=[0,20]) - translate([x,0]) - circleat(c, hp_rn); - circleat(hppB, hp_rn); + for (s = [-1,+1]) { + c = s > 0 ? hppT : hppB; + translate(c) + mirror([0,0,s>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(0); + children(); +} + +module CatchPortion(xwidth, ztop){ + width = catch_width + xwidth; + w = width + catch_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 - catch_topcurve_r]) + circle(catch_topcurve_r); + translate([w/2, -50]) + square(catch_topcurve_r*2, center=true); + } + } + } + } + } } module CaseBase(){ @@ -456,13 +1004,109 @@ module CaseBase(){ 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) - intersection(){ + Flip_rhs(1-keeper_side) intersection(){ rotate([90, 0, 0]) linear_extrude(height = phone_height + phone_cnr_rad * 2) KeeperProfile(1); @@ -471,17 +1115,26 @@ module Case(){ ////toplevel translate([0,0, -25]) linear_extrude(height = 50) hull() - // Flip_rhs() // actually, we only care about the LH Flip_bot() circleat([+1,-1] * phone_cnr_rad, phone_cnr_rad + case_th_side/2); } // 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(1) + Flip_rhs(keeper_side) translate([0, -phone_cnr_rad, 0]) rotate([90, 0, 0]) linear_extrude(height = phone_height + phone_cnr_rad * 2) @@ -500,6 +1153,7 @@ module Case(){ ////toplevel Buttons(){ mirror([1,0,0]) rotate([90,0,90]) { + if (!($button_leg_only && enable_support)) intersection(){ translate([0,0,-10]) linear_extrude(height= 20) @@ -507,21 +1161,30 @@ module Case(){ ////toplevel if ($button_leg_only) rotate([-90,90,0]) translate([phone_width/2, -400, kppe[1]]) - mirror([$rhsflip,0,0]) cube([400, 800, 50]); + 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 CaseAperture(jack_pos, jack_dia, 8); Flip_rhs(1) - CaseAperture(noisecancelmic_pos, noisecancelmic_dia, 20); + CaseAperture(noisecancelmic_pos, noisecancelmic_dia, 8); OrdinaryRearApertures(); + MicroUSB(); + // gaps for the lid's hinge arms HingePortion(hex20 - hinge_x_arms_gap, hex21 + hinge_x_arms_gap) @@ -529,14 +1192,63 @@ module Case(){ ////toplevel HingeLidProfile(); circle(r= hinge_r_arms_gap, $fn= 8); } + + // screw holes in the hinge arms + HingeScrews(); + + // catch striker + CatchPortion(catch_side_gap*2, epp4[1]) + CatchCutProfile(); + + // 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(){ - AroundEdges(lpp10[1], lpp13[1] - lpp10[1], 0) - LidEdgeProfile(); + 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(){ @@ -553,10 +1265,42 @@ module Lid(){ ////toplevel } // hinge arms - HingePortion(hex20, hex21) HingeLidProfile(); + HingePortion(hex20, hex21) { + LidEdgeProfile(); + HingeLidProfile(); + } + + // catch + CatchPortion(0, lpp12[1]) + CatchCatchProfile(); } 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 @@ -566,11 +1310,55 @@ module HingeLever(){ ////toplevel HingeLeverOuterProfile(); // space for the screws - HingePortion(hex23, hex24) + HingePortion(hex26, hex24) HingeLeverInnerProfile(); + + // recesses for the nuts + HingePortion(hex23, hex26+1) + HingeLeverNutProfile(); + + // bores for the screws + HingeScrews(); + + // space for the charging cable + MicroUSB(); + Flip_hinge() MicroUSB(); + } +} + +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]); @@ -683,6 +1471,12 @@ module TestSelectFrame(){ } } +module TestSelectLidFrame(){ + TestSelectFrame(); + translate([led_pos[0], -led_pos[1], -50]) + cylinder(r= nla_r2+3, h=100); +} + module TestFrameCase(){ ////toplevel intersection(){ Case(); @@ -694,16 +1488,41 @@ module TestFrameCase(){ ////toplevel } } -module TestFrameLidPrint(){ ////toplevel - rotate([0,0,180]) intersection(){ - Lid(); +module TestSelectTopApertures(){ + translate([-100, -35, -100]) + cube([400, 100, 200]); + LidAdhocMultiprintFrame(0); + LidAdhocMultiprintFrame(1); +} + +module TestTopApertures(){ ////toplevel + intersection(){ + Case(); TestSelectFrame(); + TestSelectTopApertures(); } } -module Keeper(){ ////toplevel - Flip_rhs() - OneKeeper(); +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){ @@ -711,47 +1530,141 @@ module ButtonPlanForDemo(z, deep, cut){ ButtonPlan(8, deep, cut); } -module DemoHingeScrews(){ +module HingeScrews(){ Flip_rhs() Flip_bot(1){ for (c= [ hppT, hppB ]) translate([ hex20, -c[0], c[1] ]){ rotate([0,90,0]) - translate([0,0,-.1]) - cylinder( r= hingescrew_shaft_dia/2, h = hingescrew_shaft_len+0.1 ); + 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") TestFrameCase(); - color("blue") intersection(){ Lid(); TestSelectFrame(); } - color("black") DemoHingeScrews(); + color("blue") intersection(){ Lid(); TestSelectLidFrame(); } + color("black") HingeScrews(); %HingeLever(); } -module DemoHingedFrame(){ ///toplevel - hinge_origin = [0, -(phone_height - hppB[0]), hppB[1]]; +module DemoLanyardCutout(){ ////toplevel + LanyardCutout(25); +} +module DemoHingedFrame(){ ///toplevel color("red") TestFrameCase(); translate([0,0, -2*hp_k]) - color("blue") intersection(){ Lid(); TestSelectFrame(); } - - translate(hinge_origin) - rotate([180,0,0]) - translate(-hinge_origin) { - color("orange") HingeLever(); - color("black") DemoHingeScrews(); - } + color("blue") intersection(){ Lid(); TestSelectLidFrame(); } + + Flip_hinge(){ + color("orange") HingeLever(); + color("black") HingeScrews(); + } } module DemoHinge(){ ////toplevel - DemoFrame(); - translate([0,0, -hp_k*3]) - DemoHingedFrame(); + translate([ -0.5*phone_width, phone_height, hp_k*3 ]) { + DemoFrame(); + translate([0,0, -hp_k*3]) + DemoHingedFrame(); + } } module DemoProfiles(){ ////toplevel @@ -759,6 +1672,26 @@ module DemoProfiles(){ ////toplevel %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(); @@ -785,7 +1718,46 @@ module DemoProfiles(){ ////toplevel color("blue") HingeLidProfile(); color("red") HingeBaseProfile(); color("black") translate([0,0,-2]) HingeLeverOuterProfile(); - color("orange") translate([0,0,-1]) HingeLeverInnerProfile(); + } + + 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]) CatchCutProfile(); + color("blue") CatchCatchProfile(); + color("red") difference(){ EdgeProfile(); CatchCutProfile(); } + } + + translate([40,-30]) { + difference(){ + LidEdgeProfile(); + translate(prlp10) + PropProfile(10, 1, 0); + } + translate(prlp10) + PropProfile(15, 0); + } + translate([60,-30]) { + PropAggregateProfile(); } } @@ -797,3 +1769,4 @@ module DemoProfiles(){ ////toplevel //LidEdgeProfile(); //KeeperProfile(); //DemoProfiles(); +//PropRecess();