The #1 Reason to have a 3D Printer.
3D-printing is becoming an increasingly ubiquitous tool. It’s especially useful for fabricating objects that you might uniquely need: objects that are useful to you, but almost no one else. I’ve had a few. Currently I own an Artillery Sidewinder X1, which I’ve had for a year. I use it so often that I’ve needed to rebuild the extruder assembly once, but besides that it’s been a very capable device.
I print all sorts of stuff. Little models for kids to paint, fruit juicers, musubi mashers. However, amongst all the things, I print one particular thing about 10x more. It saves me thousands of dollars a year, far more than the cost of a 3D printer. The object, is a humble phone case:
This phone case is slightly strengthened relative to an original design by Peter Holzwarth. It’s easily customizable thanks to the simple language for functional 3d geometry definition, openSCAD (which is FOSS).
Anytime I get a new phone for myself, my kids, etc. I pull out some 4$ Harbor Freight calipers. I adjust the height width and depth in the code and I print one of these cases out. If the phone is dropped, this case works like a bike-helmet. A corner pops off and the phone is fine. Then I print another. It also holds the phone a few millimeters off a surface, such that the phone is held above any spills. It holds most objects away from the screen and prevents scratches too.
I have printed about 20-25 of these, saving about 20-25 phone screens. Using this case, a 5 year old can have a phone without a broken screen. It’s pretty remarkable. The code for this thing below.
// **************************************************************************** | |
// Customizable Smartphone Holders | |
// to mount your smartphone to a tripod or elsewhere using an action cam hook | |
// Author: Peter Holzwarth | |
// **************************************************************************** | |
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language | |
SmartphoneHolder([PhoneName, PhoneWidth, PhoneHeight, PhoneDepth, | |
PhoneCornerRoundingRadius, PhoneEdgeRoundingRadius, | |
PhoneCamPos, CamSlotHeight], MountPos); | |
// for manual rendering: | |
// SmartphoneHolder(phone=PhoneiPhone8,mountPos="bottom"); | |
// render only the middle hook | |
// MiddleHook("female"); | |
// parameters for thingiverse customizer: | |
/* [Phone] */ | |
// Name of the phone | |
PhoneName= "MiMix2"; | |
// Width of the phone | |
PhoneWidth= 76.7; | |
// Height of the phone | |
PhoneHeight= 165.8; | |
// Depth/ thickness of the phone - add 0,5mm here! | |
PhoneDepth=8.8; | |
// Rounding radius of the four corners | |
PhoneCornerRoundingRadius= 10; | |
// Rounding radius of the edges at all sides | |
PhoneEdgeRoundingRadius= 4.; | |
// Position of the camera, when looking from the front | |
PhoneCamPos= 0; // [0:Mid,1:Left,2:Right] | |
// Height of the space for the camera (only required if on left/right side): | |
CamSlotHeight= 18; | |
/* [Mounting] */ | |
// Mount at bottom or mid of holder | |
MountPos= "mid"; // [bottom,mid] | |
// Bracket thickness | |
th= 2; | |
// Bottom thickness | |
bt= 4; | |
// Precision of curves | |
$fn=85; | |
/* [Hidden] */ | |
// constants for the camera position | |
CamPosMid= 0; | |
CamPosLeft= 1; | |
CamPosRight= 2; | |
// you may use these pre-defined models in the call below | |
PhoneSamsungS4= ["Samsung S4", 70, 137, 8, 12, 2, CamPosMid]; | |
PhoneSamsungS5= ["Samsung S5", 73, 143, 8.5, 12, 2, CamPosMid]; | |
PhoneSamsungS6= ["Samsung S6", 70, 144, 8, 12, 2, CamPosMid]; | |
PhoneSamsungS7= ["Samsung S7", 71, 144, 8, 12, 2, CamPosMid]; | |
PhoneSamsungS8= ["Samsung S8", 68, 149.5, 8.5, 12, 2.5, CamPosMid]; | |
// Blank holder | |
//translate([PhoneWidth/2,PhoneHeight/4,(th+bt)/2]) | |
//union(){ | |
// translate([0,4,(th+bt)]) | |
// rotate([-90,0,0]) | |
// rotate([0,90,0]) | |
// goldbar(length=2*26/3, width=(25.5/3-0.3), height=((th+bt)/2-0.3), angle=-70.); | |
// difference(){ | |
// cubeR([26,26,th+bt], 2, true); | |
// translate([0,0,-10]) | |
// fourScrews(16,16,3,80); | |
// } | |
//}; | |
// Loop holder. | |
//translate([PhoneWidth/2,0,13-(th+bt)/2]) | |
//rotate([0,90,90]) | |
//union(){ | |
// translate([0,4,(th+bt)]) | |
// rotate([-90,0,0]) | |
// rotate([0,90,0]) | |
// translate([0,0.3,0]) | |
// goldbar(length=2*26/3, width=(25.5/3-0.3), height=((th+bt)/2-0.3), angle=-70.); | |
// translate([0,3,-13]) | |
// rotate([90,0,0]) | |
// ringx(h=(th+bt),od=26,id=18); | |
// difference(){ | |
// cubeR([26,26,th+bt], 2, true); | |
//// translate([0,0,-10]) | |
//// fourScrews(16,16,3,80); | |
// } | |
//}; | |
//Cable tie holder | |
//translate([PhoneWidth/2,0,13-(th+bt)/2]) | |
//rotate([0,90,90]) | |
//union(){ | |
// translate([0,4,(th+bt)]) | |
// rotate([-90,0,0]) | |
// rotate([0,90,0]) | |
// translate([0,0.3,0]) | |
// goldbar(length=2*26/3, width=(25.5/3-0.3), height=((th+bt)/2-0.3), angle=-70.); | |
// translate([0,3,-13]) | |
// rotate([90,0,0]) | |
// ringx(h=(th+bt),od=26,id=18); | |
// difference(){ | |
// cubeR([26,26,th+bt], 2, true); | |
// translate([0,0,-10]) | |
// fourScrews(16,16,3,80); | |
// } | |
//}; | |
module ringx(h=10,od = 10,id = 5,de = 0.1,sr=1 | |
) | |
{ | |
minkowski(){ | |
sphere(sr,$fn=9); | |
difference() { | |
cylinder(h=h, r=od/2-sr,$fn=24); | |
translate([0, 0, -de]) | |
cylinder(h=h+2*de, r=id/2+sr,$fn=24); | |
} | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// Holder | |
module SmartphoneHolder(phone=PhoneSamsungS6, mountPos="mid") { | |
SmartphoneHolderI(phone, mountPos); | |
} | |
function parametersHaveErrors(phone) = | |
phone[1]+phone[2]+phone[3]+phone[4]+phone[5]==undef; | |
module SmartphoneHolderI(phone, mountPos) { | |
// the four corners | |
// lower left | |
if (phone[6]!=CamPosLeft) { | |
HolderCorner(phone); | |
} else { | |
HolderEdge(phone); | |
} | |
// lower right | |
translate([phone[2]+2*th,0,0]) rotate([0,0,90]) | |
HolderCorner(phone); | |
// upper left | |
if (phone[6]!=CamPosRight) { | |
translate([0,phone[1]+2*th,0]) rotate([0,0,-90]) | |
HolderCorner(phone); | |
} else { | |
translate([0,phone[1]+2*th,0]) | |
mirror([0,1,0]) HolderEdge(phone); | |
} | |
// upper right | |
translate([phone[2]+2*th,phone[1]+2*th,0]) rotate([0,0,180]) | |
HolderCorner(phone); | |
difference(){ | |
union(){ | |
// the oval half rings | |
rd= phone[4]+th; | |
// left half ring | |
difference() { | |
translate([th+2,th+phone[1]/2,0]) rotate([0,0,-90]) | |
halfOvalRing(phone[1]/2,phone[2]/2,bt+th,th,phone[5]); | |
// remove outer roundings from the oval rings | |
roundCut(phone); | |
translate([0,phone[1]+2*th,-0.01]) rotate([0,0,-90]) | |
roundCut(phone); | |
} | |
// right half ring | |
difference() { | |
translate([th+phone[2]-2,th+phone[1]/2,0]) rotate([0,0,90]) | |
halfOvalRing(phone[1]/2,phone[2]/2,bt+th,th,phone[5]); | |
translate([phone[2]+2*th,0,-0.01]) rotate([0,0,90]) | |
roundCut(phone); | |
translate([phone[2]+2*th,phone[1]+2*th,-0.01]) rotate([0,0,180]) | |
roundCut(phone); | |
} | |
translate([th+phone[2]/2,th+phone[1]/2,0]) | |
translate([0,0,(th+bt)/2]) | |
cubeR([26,26,th+bt], 2, true); | |
} | |
translate([th+phone[2]/2,th+phone[1]/2,0]) | |
translate([-4,4,(th+bt)/2]) | |
rotate([-90,0,0]) | |
rotate([0,90,0]) | |
goldbar(length=3*26/4, width=26/3, height=(th+bt)/2, angle=-70.); | |
} | |
} | |
module male_slottedcube(dims, rnd=1, centerR= false) { | |
union(){ | |
translate(centerR?[-dims[0]/2,-dims[1]/2,-dims[2]/2]:[]) { | |
hull() { | |
translate([rnd,rnd,rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,rnd,rnd]) sphere(r=rnd); | |
translate([rnd,dims[1]-rnd,rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,dims[1]-rnd,rnd]) sphere(r=rnd); | |
translate([rnd,rnd,dims[2]-rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,rnd,dims[2]-rnd]) sphere(r=rnd); | |
translate([rnd,dims[1]-rnd,dims[2]-rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,dims[1]-rnd,dims[2]-rnd]) sphere(r=rnd); | |
} | |
} | |
translate([0.,0.,dims[2]]) | |
mirror([0.,0.,1.]) | |
translate([-dims[1]/6,0.,0.]) | |
rotate([90.,0.,0.]) | |
goldbar(length=dims[0]/2, width=dims[1]/3, height=dims[2]/2, angle=-70.); | |
} | |
} | |
module female_slottedcube(dims, rnd=1, centerR= false) { | |
difference(){ | |
translate(centerR?[-dims[0]/2,-dims[1]/2,-dims[2]/2]:[]) { | |
hull() { | |
translate([rnd,rnd,rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,rnd,rnd]) sphere(r=rnd); | |
translate([rnd,dims[1]-rnd,rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,dims[1]-rnd,rnd]) sphere(r=rnd); | |
translate([rnd,rnd,dims[2]-rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,rnd,dims[2]-rnd]) sphere(r=rnd); | |
translate([rnd,dims[1]-rnd,dims[2]-rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,dims[1]-rnd,dims[2]-rnd]) sphere(r=rnd); | |
} | |
} | |
translate([-dims[1]/6,-dims[0]/4,0.]) | |
rotate([90.,0.,0.]) goldbar(length=dims[0], width=dims[1]/3, height=dims[2]/2, angle=-70.); | |
} | |
} | |
module goldbar(length=30, width=20, height=10, angle=60){ | |
// Angle is fixed at 60 | |
linear_extrude(height = length, center = true, convexity = 10) | |
polygon(points=[[0,0],[width,0],[width - cos(angle)*height,height],[cos(angle)*height,height]]); | |
} | |
module roundCut(phone) { | |
rd= phone[4]+th; | |
difference() { | |
cube([rd, rd, rd+th+bt]); | |
translate([rd,rd,-0.01]) cylinder(r=phone[4],h=rd+th+bt+0.02); | |
} | |
} | |
module MiddleHook(hooks) { | |
h= 20; | |
difference() { | |
union() { | |
translate([0,0,(th+bt)/2]) cubeR([26,26,th+bt],2, true); | |
if (hooks == "male") { | |
translate([0,0,h]) ActionCamRingsMale(h, true); | |
} else { | |
translate([0,0,h]) ActionCamRingsFemale(h, true); | |
} | |
} | |
translate([0,0,-0.01]) fourScrews(16,16,3,th+bt+0.02); | |
translate([0,0,(th+bt)/2]) fourScrews(16,16,6.3,(th+bt)/2+2,6); | |
} | |
} | |
// phone=PhoneSamsungS6; | |
// ovalRing(phone[1]/2,phone[2]/2,bt+th,2+th,phone[5],180); | |
// halfOvalRing(phone[1]/2,phone[2]/2,bt+th,2+th,phone[5],180); | |
// //////////////////////////////////////////////////////////////// | |
// Oval ring with a constant diameter | |
// Used to form the arms | |
module halfOvalRing(w, d, h, rth, rr) { | |
difference() { | |
difference() { | |
scale([1,d/w,1]) fring(w-rth/2, h); | |
translate([-0.01,-0.01,-0.01]) scale([1,(d-rth)/(w-rth),1]) difference() { | |
cylinder(r=w-rth, h=h+0.02); | |
translate([0,0,h/2]) ring(w-rth, h); | |
} | |
} | |
translate([-w-th-0.01,-d-3*th-0.05,-0.01]) | |
cube([2*(w+th), d+3*th+0.05, h+0.03]); | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// One corner of the holder | |
module HolderCorner(phone) { | |
difference() { | |
hull() { | |
wl= max(phone[4]+th,10); | |
tl= max(phone[5],3); | |
// corner | |
translate([phone[4]+th,phone[4]+th,0]) rotate([0,0,180]) | |
qwheel(phone[4]+th+2.5,phone[3]+2*th+bt,phone[5]); | |
// x longhole | |
translate([wl,tl,phone[3]+phone[5]+bt]) rotate([0,90,0]) | |
longHole(radius=tl, length=phone[3]-2*tl+2*th+bt, | |
height=2); | |
// y longhole | |
translate([tl,wl,phone[3]+phone[5]+bt]) rotate([0,90,90]) | |
longHole(radius=tl, length=phone[3]-2*tl+2*th+bt, | |
height=2); | |
} | |
translate([th,th,th+bt]) Smartphone(phone); | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// Edge, used for smartphones with the camera at one side | |
module HolderEdge(phone) { | |
h= bt+th; | |
y2= phone[2]/2-sqrt((phone[2]/2)*(phone[2]/2)-phone[7]*phone[7])+2*(bt+th); | |
difference() { | |
union() { | |
// upper part - short to not cover the display much | |
translate([phone[7],0,0]) cubeR([15,8,phone[3]+2*th+bt],2, false); | |
// lower part - longer to cover the arm | |
translate([phone[7],0,0]) cubeR([15,y2,h+2],2, false); | |
} | |
translate([th,th,th+bt]) Smartphone(phone); | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// Smartphone model | |
module Smartphone(phone=PhoneSamsungS6) { | |
hull() { | |
translate([phone[2]-phone[4],phone[1]-phone[4],0]) | |
qwheel(phone[4],phone[3],phone[5]); | |
translate([phone[2]-phone[4],phone[4],0]) rotate([0,0,-90]) | |
qwheel(phone[4],phone[3],phone[5]); | |
translate([phone[4],phone[4],0]) rotate([0,0,180]) | |
qwheel(phone[4],phone[3],phone[5]); | |
translate([phone[4],phone[1]-phone[4],0]) rotate([0,0,90]) | |
qwheel(phone[4],phone[3],phone[5]); | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// a quater wheel with an outer wheel radius, an outer height and a rounding radius | |
module qwheel(wr, h, rr, center=false) { | |
translate([0,0,center?-h/2:0]) { | |
hull() { | |
translate([0,0,h-rr]) qwheelBike(wr, rr); | |
translate([0,0,rr]) qwheelBike(wr, rr); | |
} | |
} | |
} | |
// quater ring, also works with older OpenSCAD versions | |
module qwheelBike(wr, rr) { | |
difference() { | |
rotate_extrude(convexity= 2) translate([wr-rr, 0, 0]) circle(r=rr); | |
translate([-wr-0.01,-wr-0.01,-rr-0.01]) cube([2*wr+0.02,wr+0.01,2*rr+0.02]); | |
translate([-wr-0.01,-wr-0.01,-rr-0.01]) cube([wr+0.01,2*wr+0.02,2*rr+0.02]); | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// rounded button - lower part is a cylinder, upper part like a filled ring | |
// r= outer radius | |
// h= height | |
// rr= upper rounding | |
module buttonRound(r, h, rr) { | |
hull() { | |
translate([0,0,h-rr]) wheelBike(r, rr); | |
cylinder(r=r,h=h-rr); | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// a ring with a radius and a thickness | |
// else same as wheelBike | |
// r - radius | |
// th - thickness | |
module ring(r, th) { | |
rotate_extrude(convexity= 4, $fn= 100) | |
translate([r, 0, 0]) | |
circle(r = th/2, $fn = 100); | |
} | |
// //////////////////////////////////////////////////////////////// | |
// filled ring - like a round cheese | |
// fring(20,4); | |
module fring(r, th) { | |
rotate_extrude(convexity= 4, $fn= 100) { | |
translate([r, th/2, 0]) | |
circle(r = th/2, $fn = 100); | |
square([r,th]); | |
} | |
} | |
// generic long hole | |
module longHole(radius, length, height) { | |
hull() { | |
cylinder(r=radius, h=height); | |
translate([length,0,0]) cylinder(r=radius, h=height); | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// T-slot for mounting onto | |
// //////////////////////////////////////////////////////////////// | |
// cube with rounded corners | |
module cubeR(dims, rnd=1, centerR= false) { | |
translate(centerR?[-dims[0]/2,-dims[1]/2,-dims[2]/2]:[]) { | |
hull() { | |
translate([rnd,rnd,rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,rnd,rnd]) sphere(r=rnd); | |
translate([rnd,dims[1]-rnd,rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,dims[1]-rnd,rnd]) sphere(r=rnd); | |
translate([rnd,rnd,dims[2]-rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,rnd,dims[2]-rnd]) sphere(r=rnd); | |
translate([rnd,dims[1]-rnd,dims[2]-rnd]) sphere(r=rnd); | |
translate([dims[0]-rnd,dims[1]-rnd,dims[2]-rnd]) sphere(r=rnd); | |
} | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// two rings for action cam mount | |
module ActionCamRingsMale(l, base, adj=-0.25, center= true) { | |
translate(center ? [(-9-adj)/2,0,0] : [0,6,l]) { | |
if (base) { | |
translate([0,-6,-l]) cube([9+adj, 12, 3]); | |
} | |
for (i= [0, 6]) { | |
translate ([i, 0, 0]) ActionCamOneRing(l, adj); | |
} | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// three rings for action cam mount | |
module ActionCamRingsFemale(l, base, adj= -0.25, center= true) { | |
translate(center ? [(-15-adj)/2,0,0] : [0,6,l]) { | |
if (base) { | |
translate([0,-6,-l]) cube([15+adj, 12, 3]); | |
} | |
for (i= [0, 6, 12]) { | |
translate ([i, 0, 0]) ActionCamOneRing(l, adj); | |
} | |
} | |
} | |
// one ring, helper for action cam hooks | |
module ActionCamOneRing(l, adj) { | |
rotate([90, 0, 90]) difference() { | |
hull() { | |
cylinder(r=6, h=3+adj); | |
translate([-6, -l, 0]) cube([12,1,3+adj]); | |
} | |
translate([0,0,-0.01]) cylinder(r=3, h=3.02+adj); | |
} | |
} | |
// four holes in a square | |
// x, y: distances of mid points | |
// d: screw diameter | |
// h: nut height | |
module fourScrews(x, y, d, h, fn=20) { | |
translate([x/2,y/2,0]) cylinder(r=d/2,h=h,$fn=fn); | |
translate([-x/2,y/2,0]) cylinder(r=d/2,h=h,$fn=fn); | |
translate([x/2,-y/2,0]) cylinder(r=d/2,h=h,$fn=fn); | |
translate([-x/2,-y/2,0]) cylinder(r=d/2,h=h,$fn=fn); | |
} | |
// //////////////////////////////////////////////////////////////// | |
// A parallelpiped slot to join halves with difference. | |
module slot() { | |
} | |
// //////////////////////////////////////////////////////////////// | |
// a pipe - a hollow cylinder | |
module pipe(outerRadius, thickness, height) { | |
difference() { | |
cylinder(h=height, r=outerRadius); | |
translate([0, 0, -0.1]) cylinder(h=height+0.2, r=outerRadius-thickness); | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// a half pipe | |
module halfPipe(outerRadius, thickness, height) { | |
difference() { | |
pipe(outerRadius, thickness, height); | |
translate([-outerRadius, -outerRadius, -0.01]) | |
cube([2*outerRadius, outerRadius, height+0.02], center=false); | |
} | |
} | |
// //////////////////////////////////////////////////////////////// | |
// a quarter of a pipe | |
module quarterPipe(outerRadius, thickness, height) { | |
difference() { | |
pipe(outerRadius, thickness, height); | |
translate([-outerRadius, -outerRadius, -0.01]) | |
cube([2*outerRadius, outerRadius, height+0.02], center=false); | |
translate([-outerRadius, 0, -0.01]) | |
cube([outerRadius, outerRadius, height+0.02], center=false); | |
} | |
} |