Created
February 17, 2019 20:03
-
-
Save ednisley/174a9d24b97cfa4bc1e06b0c17ed9cef to your computer and use it in GitHub Desktop.
OpenSCAD source code: SJCAM M20 mount for Tour Easy seat back rail
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SJCAM M20 Camera Mount for Tour Easy seat back rail | |
// Ed Nisley - KE4ZNU | |
// 2019-02 | |
/* [Layout Options] */ | |
Layout = "Fit"; // [Show,Fit,Build] | |
Part = "Shell"; // [Cradle,Shell,Clamp,ShellSections,M20,Interposer,Battery,Buttons] | |
LookAngle = [0,5,-25]; // camera angle, looking backwards | |
/* [Extrusion Parameters] */ | |
ThreadWidth = 0.40; | |
ThreadThick = 0.25; | |
HoleWindage = 0.2; | |
Protrusion = 0.1; | |
//----- | |
// Dimensions | |
/* [Hidden] */ | |
ID = 0; | |
OD = 1; | |
LENGTH = 2; | |
ClampScrew = [5.0,10.0,50.0]; // ID=thread OD=washer LENGTH=total | |
ClampInsert = [5.0,7.5,10.5]; // brass insert | |
MountScrew = [3.0,7.0,23]; // ID=thread OD=washer LENGTH=tune to fit clamp arch | |
MountInsert = [3.0,4.95,8.0]; // ID=screw OD, OD=knurl dia | |
EmbossDepth = 2*ThreadThick + Protrusion; // recess depth + Protrusion beyond surface | |
DebossHeight = EmbossDepth; // text height + Protrusion into part | |
Projection = 10; // stick-out to punch through shell sides & suchlike | |
SupportColor = "Yellow"; | |
FadeColor = "Green"; | |
FadeAlpha = 0.25; | |
//----- | |
// Useful routines | |
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit); | |
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes | |
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2); | |
FixDia = Dia / cos(180/Sides); | |
cylinder(r=(FixDia + HoleWindage)/2, | |
h=Height, | |
$fn=Sides); | |
} | |
//----- | |
// M20 Camera | |
// Looks backwards from seat = usual right-hand coordinates work fine | |
// X parallel to bike frame, Y parallel to seat strut, Z true vertical | |
M20 = [24.5,40.5,54.0]; | |
M20tm = 4.0; // chord height at top of case | |
M20tr = (pow(M20tm,2) + pow(M20.y,2)/4) / (2*M20tm); // ... radius | |
echo(str("Top radius: ",M20tr)); | |
M20TopSides = 3*3*4; | |
echo(str(" ... sides: ",M20TopSides)); | |
M20fm = 1.0; // chord height at front of case | |
M20fr = (pow(M20fm,2) + pow(M20.y,2)/4) / (2*M20fm); // ... radius | |
echo(str("Front radius: ",M20fr)); | |
M20FrontSides = ceil(M20fr / M20tr * M20TopSides); // make arc sides match up | |
echo(str(" ... sides: ",M20FrontSides)); | |
Lens = [19.0,22.5,5.5]; // ID=optical element, OD=tube | |
LensBezel = [23.0,24.5,2.5]; // ID=lens tube, OD=bezel | |
LensOffset = [-M20fm,0,41.5]; // bottom of case to lens centerline | |
LensCap = [Lens[OD],24.5,4.5]; // silicone lens cap | |
Spkr = [0.75,M20.y,14.3]; // speaker recess below LCD | |
Switch = [8.0,1.0,38.0]; // selection switches | |
SwitchOffset = [9.0,0,0]; // from rear to center of switches | |
Jack = [10.0,0.1,36.0]; // jack and MicroSD card access, slightly enlarged | |
JackOffset = [10.0,0,30.0]; // rear, bottom to center of jack block | |
USB = [JackOffset.x - Jack.x/2,20.0,10.0]; // strut under USB plug | |
USBOffset = [0,0,33.5]; // bottom to center of jack | |
SDCard = [2.0,0.1,12.0]; // SD Card slot | |
SDOffset = [9.0,0,20.0]; // bottom, rear to center of slot | |
Button = [8.5,10.5,M20tm]; // ID = button, OD = bezel | |
ButtonOC = 18.0; // on-center Y separation, assume X centered | |
Screen = [0.1,31,24]; // LCD on rear face | |
ScreenOffset = [0,0,33]; | |
BarLEDs = [0.1 + M20fm,12.0,5.0]; // Bar LEDs on front face | |
BarLEDsOffset = [-M20fm,0,12.5]; | |
PwrLED = [3.5,3.5,0.1 + M20tm]; // power LED on top | |
PwrLEDOffset = [2.5,0,0]; | |
RearLEDs = [1.0,2.0,0.1]; // charge and power LED openings above LCD | |
RearLEDsOffset = [0,13.0/2,M20tm + 3.0]; // .. from top center of case | |
module Buttons(KO) { | |
for (j = [-1,1]) | |
translate([0,j*ButtonOC/2,0]) { | |
cylinder(d=Button[OD],h=Button[LENGTH],$fn=12); | |
if (KO) | |
translate([0,0,M20tm]) | |
cylinder(d1=Button[OD],d2=1.5*Button[OD],h=Button.z,$fn=12); | |
} | |
} | |
module M20Shape(Knockout = false) { | |
difference() { | |
intersection() { | |
translate([0,0,M20.z/2 - M20tr]) // top curve | |
rotate([0,90,0]) rotate(180/M20TopSides) | |
cylinder(r=M20tr,h=2*(M20.x + Protrusion),$fn=M20TopSides,center=true); | |
translate([M20.x/2 - M20fr,0,0]) | |
rotate(180/M20FrontSides) | |
cylinder(r=M20fr,h=2*M20.z,$fn=M20FrontSides,center=true); | |
cube(M20,center=true); | |
} | |
translate([Spkr.x/2 - M20.x/2 - Protrusion,0,Spkr.z/2 - Protrusion/2 - M20.z/2]) | |
cube(Spkr + [Protrusion,2*Protrusion,Protrusion],center=true); | |
} | |
translate([M20.x/2,0,-M20.z/2] + LensOffset) | |
rotate([0,90,0]) | |
cylinder(d=Lens[OD] + HoleWindage,h=(Knockout ? Projection : Lens[LENGTH]),$fn=4*4*3,center=false); | |
translate([M20.x/2 + M20fm/2,0,-M20.z/2] + LensOffset) // lens bezel | |
rotate([0,90,0]) | |
cylinder(d1=LensBezel[OD],d2=Lens[OD],h=LensBezel[LENGTH],$fn=4*4*3,center=false); | |
translate([-M20.x/2 + SwitchOffset.x, // side switches | |
-(Switch.y + M20.y - Protrusion)/2, | |
0]) | |
cube(Switch + [0,Protrusion,0] + (Knockout ? [0,Projection,0] : [0,0,0]),center=true); | |
if (Knockout) | |
translate([(M20.x/2 - M20fm)/2,-M20.y/2,0]) // side switch slide-in clearance | |
cube([M20.x/2 - M20fm,2*Switch.y,Switch.z],center=true); | |
translate([-M20.x/2 + JackOffset.x, | |
(Jack.y + M20.y - Protrusion)/2, | |
JackOffset.z - M20.z/2]) | |
cube(Jack + [0,Protrusion,0] + (Knockout ? [0,Projection,0] : [0,0,0]),center=true); | |
translate([0,0,M20.z/2 - M20tm]) // top control buttons | |
Buttons(Knockout); | |
if (Knockout) | |
translate([(M20.x - M20fm)/4,0,M20.z/2 - M20tm + Button[LENGTH]/2]) // slide-in button clearance | |
cube([(M20.x - M20fm)/2,ButtonOC + Button[OD],Button[LENGTH]],center=true); | |
translate([-(M20.x + Screen.x - Protrusion)/2,0,-M20.z/2] + ScreenOffset) | |
cube(Screen + [Protrusion,0,0] + (Knockout ? [Projection,0,0] : [0,0,0]),center=true); | |
for (j = [-1,1]) | |
translate([-M20.x/2 + Protrusion,j*RearLEDsOffset.y,M20.z/2 - RearLEDsOffset.z]) | |
rotate([0,-90,0]) rotate(180/6) | |
PolyCyl(RearLEDs[OD],Knockout ? Projection : RearLEDs[LENGTH],6); | |
translate([M20.x/2 + BarLEDs.x/2,0,-M20.z/2] + BarLEDsOffset) | |
cube(BarLEDs + (Knockout ? [Projection,0,0] : [0,0,0]),center=true); | |
translate([0,0,M20.z/2 - M20tm] + PwrLEDOffset) | |
rotate(180/8) | |
PolyCyl(PwrLED[OD],(Knockout ? Projection : PwrLED[LENGTH]),8); | |
if (Knockout) { | |
translate([0,0,-M20.z/2]) | |
rotate([180,0,0]) { // mounting screw | |
PolyCyl(MountScrew[ID],MountScrew[LENGTH],6); | |
translate([0,0,MountScrew[LENGTH] - Protrusion]) | |
PolyCyl(MountScrew[OD],MountScrew[ID] + 4*ThreadThick,6); // SHCS head is about 1 ID long | |
} | |
translate([0,0,-(M20.z/2 + MountInsert[LENGTH] + 4*ThreadWidth - Protrusion)]) | |
PolyCyl(MountInsert[OD],MountInsert[LENGTH] + 4*ThreadWidth,6); // insert inside Interposer | |
} | |
} | |
//----- | |
// Shell | |
// Wraps around camera | |
NomWall = 3.0; | |
ShellWall = [IntegerMultiple(NomWall,ThreadThick), | |
IntegerMultiple(NomWall,ThreadWidth), | |
IntegerMultiple(NomWall,ThreadWidth)]; | |
ShellRadius = ShellWall.x; | |
ShellSides = 8; | |
ShellOA = M20 + 2*ShellWall; | |
echo(str("Shell OA: ",ShellOA)); | |
Interposer = [M20.x - M20fm,M20.x - M20fm,10.0]; // if you can't be smart, be square | |
module Shell() { | |
Screw = [3.0,6.75,30]; // ID=thread OD=washer LENGTH | |
ScrewClear = 1.0; // additional washer clearance | |
ScrewSides = 8; | |
ScrewOC = M20 + [0,Screw[ID]/cos(180/ScrewSides),Screw[ID]/cos(180/ScrewSides)]; // use PolyCyl hole dia, ignore .x value | |
difference() { | |
union() { | |
hull() | |
for (i=[-1,1], j=[-1,1], k=[-1,1]) | |
translate([i*(ShellOA.x - 2*ShellRadius)/2, | |
j*(ShellOA.y - 2*ShellRadius)/2, | |
k*(ShellOA.z - 2*ShellRadius)/2]) | |
sphere(r=ShellRadius/cos(180/ShellSides),$fn=ShellSides); // fix low-poly approx radius | |
for (j=[-1,1], k=[-1,1]) // screw bosses, full length | |
translate([0,j*ScrewOC.y/2,k*ScrewOC.z/2]) | |
rotate([0,90,0]) rotate(180/ScrewSides) | |
cylinder(d=Screw[OD] + ScrewClear,h=ShellOA.x,center=true,$fn=ScrewSides); | |
translate([-(ShellOA.x - USB.x - ShellWall.x)/2, // USB plug support strut | |
(M20.y + USB.y)/2 - ShellRadius, | |
-M20.z/2] + USBOffset) | |
hull() | |
for (i=[-1,1], j=[-1,1], k=[-1,1]) | |
translate([i*(USB.x + ShellWall.x - 2*ShellRadius)/2, | |
j*(USB.y - 2*ShellRadius)/2, | |
k*(USB.z - 2*ShellRadius)/2]) | |
rotate(0*180/ShellSides) rotate([90,0,90]) | |
sphere(r=ShellRadius/cos(180/ShellSides),$fn=ShellSides); | |
translate([-M20fm/2,0,-ShellOA.z/2 - Interposer.z + Protrusion/2]) | |
InterposerShape(Embiggen = false); | |
} | |
render(convexity=4) // remove camera shape from interior | |
M20Shape(Knockout = true); | |
for (j=[-1,1], k=[-1,1]) // screw bores | |
translate([-ShellOA.x,j*ScrewOC.y/2,k*ScrewOC.z/2]) | |
rotate([0,90,0]) rotate(180/ScrewSides) | |
PolyCyl(Screw[ID],2*ShellOA.x,ScrewSides); | |
translate([ShellOA.x/2 - ThreadThick + Protrusion/2,0,-5]) // recess for legend | |
cube([EmbossDepth,ShellOA.y - 12,7],center=true); | |
translate([0,(M20.y + 1.5*SDCard.z)/2 + ThreadWidth,-M20.z/2 + SDOffset.z]) | |
resize([M20.x,0,0]) | |
sphere(d=1.5*SDCard.z,$fn=24); | |
} | |
translate([ShellOA.x/2 - DebossHeight,0,-5]) | |
rotate([90,0,90]) | |
linear_extrude(height=DebossHeight,convexity=20) | |
text(text="KE4ZNU",size=5,spacing=1.20,font="Arial:style:Bold",halign="center",valign="center"); | |
// Totally ad-hoc support structures | |
if (false) | |
color(SupportColor) { | |
for (j=[-1,1], k=[0,1]) | |
translate([-ShellOA.x/2 + Screw[LENGTH],j*ShellOA.y/2,k*ShellOA.z]) | |
rotate([0,90,0]) | |
SupportScrew(Dia=Screw[OD] + ScrewClear,Length=ShellOA.x - Screw[LENGTH],Num=ScrewSides); | |
} | |
} | |
// Generate support structure for screw boss | |
module SupportScrew(Dia,Length,Num = 6) { | |
for (a=[0 : 360/Num : 360/2]) | |
rotate(a) | |
translate([0,0,(Length + ThreadThick)/2]) | |
cube([Dia - 2*ThreadWidth,2*ThreadWidth,Length - ThreadThick],center=true); | |
} | |
// Generate interposer block | |
// Origin at center bottom surface for E-Z rotation | |
module InterposerShape(Embiggen = false) { | |
translate([0,0,Interposer.z/2]) | |
if (Embiggen) { | |
minkowski() { | |
cube(Interposer,center=true); | |
cube(HoleWindage,center=true); | |
} | |
} | |
else | |
cube(Interposer + [-Protrusion,0,Protrusion],center=true); // avoid slivers, merge with shell | |
} | |
// Cut shell sections for printing | |
// "Front" = lens end, toward +X direction | |
// origin centered on M20.xyz and ShellOA.xyz | |
module ShellSection(Section="Front") { | |
if (Section == "Front") // include front curve | |
intersection() { | |
Shell(); | |
translate([ShellOA.x - (M20fm + ShellWall.x),0,0]) | |
cube([ShellOA.x,2*ShellOA.y,2*ShellOA.z],center=true); | |
} | |
else if (Section == "Center") // exclude front curve for E-Z printing | |
intersection() { | |
Shell(); | |
translate([-M20fm/2,0,0]) | |
cube([M20.x - M20fm,2*ShellOA.y,2*ShellOA.z],center=true); | |
} | |
else if (Section == "Back") // flush with LCD on rear face | |
intersection() { | |
Shell(); | |
translate([-ShellOA.x + (ShellWall.x),0,0]) | |
cube([ShellOA.x,2*ShellOA.y,2*ShellOA.z],center=true); | |
} | |
} | |
//----- | |
// Clamp | |
// Grips seat frame rail | |
// Uses shell rounding values for tidiness | |
// Adjust MountScrew[LENGTH] to put head more-or-less flush with clamp arch | |
RailOD = 20.0; // slightly elliptical in bent section | |
RailSides = 2*3*4; | |
ClampOA = [60.0,40.0,ClampScrew[LENGTH]]; // set clamp size to avoid weird screw spacing | |
echo(str("Clamp OA: ",ClampOA)); | |
ClampOffset = 0.0; // raise clamp to allow more room for mount | |
ClampTop = ClampOA.z/2 + ClampOffset; | |
InsertCap = 6*ThreadThick; // fill layers atop inserts | |
Kerf = 2.0; | |
module Clamp(Support = false) { | |
RibThick = 2*ThreadWidth; | |
NumRibs = IntegerMultiple(ceil(ClampOA.y / 4.0),2); // space ribs roughly 4 mm apart | |
RibSpace = ClampOA.y / NumRibs; | |
echo(str("Ribs: ",NumRibs," spaced: ",RibSpace)); | |
ClampScrewOC = IntegerMultiple(ClampOA.x - ClampScrew[OD] - 10*ThreadWidth,1.0); | |
echo(str("ClampScrew OC: ",ClampScrewOC)); | |
difference() { | |
hull() | |
for (i=[-1,1], j=[-1,1], k=[-1,1]) | |
translate([i*(ClampOA.x - 2*ShellRadius)/2, | |
j*(ClampOA.y - 2*ShellRadius)/2, | |
k*(ClampOA.z - 2*ShellRadius)/2 + ClampOffset]) | |
sphere(r=ShellRadius/cos(180/ShellSides),$fn=ShellSides); | |
cube([2*ClampOA.x,2*ClampOA.y,Kerf],center=true); // split across middle | |
rotate([90,0,0]) // seat rail | |
cylinder(d=RailOD,h=2*ClampOA.y,$fn=RailSides,center=true); | |
for (i=[-1,1]) // clamp inserts | |
translate([i*ClampScrewOC/2,0,0]) | |
rotate(180/6) | |
PolyCyl(ClampInsert[OD],ClampTop - InsertCap,6); | |
for (i=[-1,1]) // clamp screw clearance | |
translate([i*ClampScrewOC/2,0,-(ClampOA.z/2 - ClampOffset) - InsertCap]) | |
rotate(180/6) | |
PolyCyl(ClampScrew[ID],ClampOA.z,6); | |
translate([0,0,ClampTop + 0.7*Interposer.z]) // mounting bolt hole | |
rotate(LookAngle) | |
translate([0,0,ShellOA.z/2]) { | |
M20Shape(Knockout = true); | |
translate([0,0,-ShellOA.z/2 - Interposer.z]) | |
InterposerShape(Embiggen = true); | |
} | |
translate([ClampOA.x/2 - (EmbossDepth - Protrusion)/2, // recess for LookAngle.z | |
0, | |
ClampOA.z/4 + ClampOffset]) | |
cube([EmbossDepth,17,8],center=true); | |
translate([0.3*ClampOA.x, // recess for LookAngle.z | |
-(ClampOA.y/2 - (EmbossDepth - Protrusion)/2), | |
ClampOA.z/4 + ClampOffset]) | |
cube([10,EmbossDepth,8],center=true); | |
translate([0,0,-ClampOA.z/2 + (EmbossDepth - Protrusion)/2]) // recess bottom legend | |
cube([35,10,EmbossDepth],center=true); | |
} | |
translate([ClampOA.x/2 - DebossHeight,0,ClampOA.z/4 + ClampOffset]) // LookAngle.z legend | |
rotate([90,0,90]) | |
linear_extrude(height=DebossHeight,convexity=20) | |
text(text=str(LookAngle.z),size=6,spacing=1.20, | |
font="Arial:style:Bold",halign="center",valign="center"); | |
translate([0.3*ClampOA.x,-ClampOA.y/2 + DebossHeight + Protrusion/2,ClampOA.z/4 + ClampOffset]) // LookAngle.y legend | |
rotate([90,0,00]) | |
linear_extrude(height=DebossHeight,convexity=20) | |
text(text=str(LookAngle.y),size=6,spacing=1.20, | |
font="Arial:style:Bold",halign="center",valign="center"); | |
translate([0,0,-ClampOA.z/2]) | |
linear_extrude(height=DebossHeight,convexity=20) | |
mirror([0,1,0]) | |
text(text="KE4ZNU",size=5,spacing=1.20, | |
font="Arial:style:Bold",halign="center",valign="center"); | |
if (Support) { | |
difference() { | |
color(SupportColor) | |
union() { | |
for (j=[-NumRibs/2:NumRibs/2]) | |
translate([0,j*RibSpace,0]) | |
rotate([90,0,0]) | |
cylinder(d=RailOD - 2*ThreadThick,h=RibThick,$fn=2*3*4,center=true); | |
cube([RailOD - 4*ThreadWidth,NumRibs*RibSpace,Kerf + 2*ThreadThick],center=true); | |
} | |
cube([2*ClampOA.x,2*ClampOA.y,Kerf],center=true); // split across middle | |
} | |
} | |
} | |
//----- | |
// Battery | |
// Based on Anker PowerCore, simplified shapes | |
// Includes port & button punchouts | |
Battery = [97.5,80.0,22.5]; // X=length, Y includes rounded edges, Z = Y dia | |
module BatteryShape() { | |
USB = [Projection,38,10]; // clearance around USB output ports | |
USBOffset = [0,25.5,0]; // from -Y edge to center of USB block | |
ChargeBtn = [11.0 + 5.0,10,5.0 + 5.0]; // charge level check button, enlarged | |
Btnc = ChargeBtn.z; // figure button recess into battery curve | |
Btnr = Battery.z/2; | |
Btnm = Btnr - sqrt(pow(Btnr,2) - pow(Btnc,2)/4); | |
ChargeBtnOffset = [17.0,0,0]; // from +X edge to center, centered on Z | |
BatterySides = 2*3*4; | |
hull() | |
for (j=[-1,1]) | |
translate([0,j*(Battery.y - Battery.z)/2,0]) | |
rotate([0,90,0]) | |
cylinder(d=Battery.z,h=Battery.x,$fn=BatterySides,center=true); | |
translate([(Battery.x + USB.x)/2 - Protrusion,-Battery.y/2 + USBOffset.y,0]) | |
cube(USB,center=true); | |
translate([Battery.x/2 - ChargeBtnOffset.x,Battery.y/2 + ChargeBtn.y/2 - 2*Btnm,0]) | |
cube(ChargeBtn,center=true); | |
} | |
//----- | |
// Battery cradle | |
RackWidth = 89.0; // flat width between rack rails | |
CradleWall = [4.0,4.0,3.0]; // wall thickness | |
CradleRadius = 2.0; // corner rounding | |
CradlePad = 0.5; // cushion around battery | |
BatteryBase = CradleWall.z + CradlePad; // actual bottom surface of battery | |
CradleOA = [Battery.x + 2*CradleWall.x, | |
min((Battery.y + 2*CradleWall.y),RackWidth), | |
BatteryBase + Battery.z/3]; | |
echo(str("Cradle OA: ",CradleOA)); | |
module Cradle() { | |
difference() { | |
hull() | |
for (i=[-1,1], j=[-1,1]) { // box with tidy rounded corners | |
translate([i*(CradleOA.x/2 - CradleRadius), | |
j*(CradleOA.y/2 - CradleRadius), | |
1*(CradleOA.z - CradleRadius)]) | |
sphere(r=CradleRadius,$fn=6); | |
translate([i*(CradleOA.x/2 - CradleRadius), | |
j*(CradleOA.y/2 - CradleRadius), | |
0*(CradleOA.z/2 - CradleRadius)]) | |
cylinder(r=CradleRadius,h=CradleOA.z/2,$fn=6); | |
} | |
translate([0,0,Battery.z/2 + BatteryBase]) // minus the battery | |
minkowski(convexity=3) { // ... slightly embiggened | |
BatteryShape(); | |
cube(2*CradlePad,center=true); | |
} | |
if (false) // reveal insets for debug | |
translate([0,0,-Protrusion]) | |
cube(CradleOA + [0,0,CradleOA.z],center=false); | |
translate([0,0,CradleWall.z - ThreadThick + Protrusion/2]) // recess top legend | |
cube([55,20,EmbossDepth],center=true); | |
translate([0,0,(EmbossDepth - Protrusion)/2]) // recess bottom legend | |
cube([70,15,EmbossDepth],center=true); | |
} | |
translate([0,4.0,CradleWall.z - DebossHeight - Protrusion]) | |
linear_extrude(height=DebossHeight,convexity=20) | |
text(text="PowerCore",size=6,spacing=1.20, | |
font="Arial:style:Bold",halign="center",valign="center"); | |
translate([0,-4.0,CradleWall.z - DebossHeight - Protrusion]) | |
linear_extrude(height=DebossHeight,convexity=20) | |
text(text="13000",size=6,spacing=1.20, | |
font="Arial:style:Bold",halign="center",valign="center"); | |
linear_extrude(height=DebossHeight,convexity=20) | |
mirror([0,1,0]) | |
text(text="KE4ZNU",size=10,spacing=1.20, | |
font="Arial:style:Bold",halign="center",valign="center"); | |
} | |
//----- | |
// Build things | |
// Layouts for design & tweaking | |
if (Layout == "Show") | |
if (Part == "Battery") | |
BatteryShape(); | |
else if (Part == "Buttons") | |
Buttons(); | |
else if (Part == "Interposer") | |
InterposerShape(Embiggen = false); | |
else if (Part == "Shell") | |
Shell(); | |
else if (Part == "M20") | |
M20Shape(Knockout = false); | |
else if (Part == "ShellSections") { | |
translate([ShellOA.x,0,0]) | |
ShellSection(Section="Front"); | |
translate([0,0,0]) | |
ShellSection(Section="Center"); | |
translate([-ShellOA.x,0,0]) | |
ShellSection(Section="Back"); | |
} | |
else if (Part == "Clamp") { | |
Clamp(Support = false); | |
color(FadeColor,FadeAlpha) | |
rotate([90,0,0]) | |
cylinder(d=RailOD,h=2*ClampOA.y,$fn=RailSides,center=true); | |
} | |
else if (Part == "Cradle") { | |
Cradle(); | |
translate([0,0,Battery.z/2 + CradleWall.z]) | |
color(FadeColor,FadeAlpha) | |
BatteryShape(); | |
} | |
// Build layouts for top-level parts | |
if (Layout == "Build") | |
if (Part == "Cradle") | |
Cradle(); | |
else if (Part == "Clamp") { | |
translate([0,0.7*ClampOA.y,0]) | |
difference() { | |
translate([0,0,-Kerf/2]) | |
Clamp(Support = true); | |
translate([0,0,-ClampOA.z]) | |
cube(2*ClampOA,center=true); | |
} | |
translate([0,-0.7*ClampOA.y,-0]) | |
difference() { | |
translate([0,0,-Kerf/2]) | |
rotate([0,180,0]) | |
Clamp(Support = true); | |
translate([0,0,-ClampOA.z]) | |
cube(2*ClampOA,center=true); | |
} | |
} | |
else if (Part == "Shell") { | |
translate([0,-1.2*ShellOA.y,ShellOA.x/2]) | |
rotate([0,90,180]) | |
ShellSection(Section="Front"); | |
translate([0,0,M20.x/2]) | |
rotate([0,-90,0]) | |
ShellSection(Section="Center"); | |
translate([0,1.4*ShellOA.y,ShellOA.x/2]) | |
rotate([0,-90,180]) | |
ShellSection(Section="Back"); | |
} | |
// Ad-hoc arrangement to see how it all goes together | |
if (Layout == "Fit") { | |
rotate(180) { | |
Cradle(); | |
translate([0,0,Battery.z/2 + CradleWall.z]) | |
color(FadeColor,FadeAlpha) | |
BatteryShape(); | |
} | |
translate([0,-100,0]) { | |
Clamp(); | |
color(FadeColor,FadeAlpha) | |
rotate([90,0,0]) | |
cylinder(d=RailOD,h=2*ClampOA.y,$fn=RailSides,center=true); | |
} | |
translate([0,-100,(ClampOA.z + ShellOA.z)/2 + Interposer.z]) | |
translate([0,0,-ShellOA.z/2 + Interposer.z]) | |
rotate(LookAngle) | |
translate([0,0,ShellOA.z/2]) { | |
Shell(); | |
color(FadeColor,FadeAlpha) | |
M20Shape(Knockout = false); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
More details on my blog at https://wp.me/poZKh-82C