Skip to content

Instantly share code, notes, and snippets.

@ednisley
Created February 17, 2019 20:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ednisley/174a9d24b97cfa4bc1e06b0c17ed9cef to your computer and use it in GitHub Desktop.
Save ednisley/174a9d24b97cfa4bc1e06b0c17ed9cef to your computer and use it in GitHub Desktop.
OpenSCAD source code: SJCAM M20 mount for Tour Easy seat back rail
// 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);
}
}
@ednisley
Copy link
Author

More details on my blog at https://wp.me/poZKh-82C

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment