Tour Easy Daytime Running Light: Annotation

The flashlight mount need not be symmetric after applying all the rotations, so recording how it’s aimed and which end goes forward seemed appropriate:

Fairing Flashlight Mount - Mount Annotation
Fairing Flashlight Mount – Mount Annotation

Optionally, with rounded ends just for pretty:

Fairing Flashlight Mount - Mount Annotation - rounded
Fairing Flashlight Mount – Mount Annotation – rounded

Because the rounding comes from resized spheres, the plate gets a ridge along the top to (maybe) lock the nylon screws / wing nuts in place:

Fairing Flashlight Mount - Mount - rounded
Fairing Flashlight Mount – Mount – rounded

Or discourage them from turning, which would be OK, too. After the second tightening, they don’t seem to come loose, so this may be overthinking the problem.

All in all, they look pretty good in cyan PETG:

Fairing Flashlight Mount - rounded
Fairing Flashlight Mount – rounded

Believe it or not, that’s aimed so the top edge of the beam is roughly horizontal to keep the hot spot out of oncoming traffic. They’re plenty bright, even on the “low power” setting.

The flashlight mounting balls produce a decorative brim that ought to be useful for something:

Slotted ball on platform
Slotted ball on platform

Maybe earrings?

The OpenSCAD source code as a GitHub Gist:

// Tour Easy Fairing Flashlight Mount
// Ed Nisley KE4ZNU - July 2017
// August 2017 -
/* [Build Options] */
FlashName = "AnkerLC40"; // [AnkerLC40,AnkerLC90,J5TactV2,InnovaX5]
Component = "Plates"; // [Ball, BallClamp, Mount, Plates, Bracket]
Layout = "Build"; // [Build, Show]
Support = false;
MountSupport = true;
/* [Extrusion] */
ThreadThick = 0.25; // [0.20, 0.25]
ThreadWidth = 0.40; // [0.40]
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
Protrusion = 0.01; // [0.01, 0.1]
HoleWindage = 0.2;
/* [Fairing Mount] */
ToeIn = 0; // inward from ahead
Tilt = 20; // upward from forward (M=20 E=15)
Roll = 0; // outward from top
Shift = 0; // Finagle Constant for support ribs
//- Screws *c
/* [Hidden] */
ID = 0;
OD = 1;
LENGTH = 2;
/* [Screws and Inserts] */
ClampInsert = [3.0,4.2,8.0];
ClampScrew = [3.0,5.9,35.0]; // thread dia, head OD, screw length
ClampScrewWasher = [3.0,6.75,0.5];
ClampScrewNut = [3.0,6.1,4.0]; // nyloc nut
/* [Hidden] */
F_NAME = 0;
F_GRIPOD = 1;
F_GRIPLEN = 2;
LightBodies = [
["AnkerLC90",26.6,48.0],
["AnkerLC40",26.6,55.0],
["J5TactV2",25.0,30.0],
["InnovaX5",22.0,55.0]
];
//- Fairing Bracket
// Magic numbers taken from the actual fairing mount
/* [Hidden] */
inch = 25.4;
BracketHoleOD = 0.25 * inch; // 1/4-20 bolt holes
BracketHoleOC = 1.0 * inch; // fairing hole spacing
// usually 1 inch, but 15/16 on one fairing
Bracket = [48.0,16.3,3.6 - 0.6]; // fairing bracket end plate overall size
BracketHoleOffset = (3/8) * inch; // end to hole center
BracketM = 3.0; // endcap arc height
BracketR = (pow(BracketM,2) + pow(Bracket[1],2)/4) / (2*BracketM); // ... radius
//- Base plate dimensions
Plate = [100.0,30.0,6*ThreadThick + Bracket[2]];
PlateRad = Plate[1]/4;
RoundEnds = true;
echo(str("Base plate thick: ",Plate[2]));
//- Select flashlight data from table
echo(str("Flashlight: ",FlashName));
FlashIndex = search([FlashName],LightBodies,1,0)[F_NAME];
//- Set ball dimensions
BallWall = 5.0; // max ball wall thickness
echo(str("Ball wall: ",BallWall));
BallOD = IntegerMultiple(LightBodies[FlashIndex][F_GRIPOD] + 2*BallWall,1.0);
echo(str(" OD: ",BallOD));
BallLength = IntegerMultiple(min(sqrt(pow(BallOD,2) - pow(LightBodies[FlashIndex][F_GRIPOD],2)) - 2*4*ThreadThick,
LightBodies[FlashIndex][F_GRIPLEN]),1.0);
echo(str(" length: ",BallLength));
BallSides = 8*4;
//- Set clamp ring dimensions
ClampOD = 50;
echo(str("Clamp OD: ",ClampOD));
ClampLength = min(20.0,0.75*BallLength);
echo(str(" length: ",ClampLength));
ClampScrewOC = IntegerMultiple((ClampOD + BallOD)/2,1);
echo(str(" screw OC: ",ClampScrewOC));
//- Adjust hole diameter to make the size come out right
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);
}
//- Fairing Bracket
// This part of the fairing mount supports the whole flashlight mount
// Centered on screw hole
module Bracket() {
linear_extrude(height=Bracket[2],convexity=2)
difference() {
translate([(Bracket[0]/2 - BracketHoleOffset),0,0])
offset(delta=ThreadWidth)
intersection() {
square([Bracket[0],Bracket[1]],center=true);
union() {
for (i=[-1,0,1]) // middle circle fills gap
translate([i*(Bracket[0]/2 - BracketR),0])
circle(r=BracketR);
}
}
circle(d=BracketHoleOD/cos(180/8),$fn=8); // dead center at the origin
}
}
//- General plate shape
// Centered on the hole for the fairing bracket
module PlateBlank() {
difference() {
translate([BracketHoleOC,0,0])
intersection() {
translate([0,0,Plate[2]/2]) // select upper half of spheres
cube(Plate,center=true);
hull()
if (RoundEnds)
for (i=[-1,1])
translate([i*(Plate[0]/2 - PlateRad),0,0])
resize([Plate[1]/2,Plate[1],2*Plate[2]])
sphere(r=PlateRad); // nice round ends!
else
for (i=[-1,1], j=[-1,1])
translate([i*(Plate[0]/2 - PlateRad),j*(Plate[1]/2 - PlateRad),0])
resize([2*PlateRad,2*PlateRad,2*Plate[2]])
sphere(r=PlateRad); // nice round corners!
}
translate([2*BracketHoleOC,0,-Protrusion]) // punch screw holes
PolyCyl(BracketHoleOD,2*Plate[2],8);
translate([0,0,-Protrusion])
PolyCyl(BracketHoleOD,2*Plate[2],8);
}
}
//- Inner plate
module InnerPlate() {
difference() {
PlateBlank();
translate([0,0,Plate[2] - Bracket[2] + Protrusion]) // punch fairing bracket
Bracket();
}
}
//- Slotted ball around flashlight
// Print with brim to ensure adhesion!
module SlotBall() {
NumSlots = 8*2; // must be even, half cut from each end
SlotWidth = 2*ThreadWidth;
SlotBaseThick = 10*ThreadThick; // enough to hold finger ends together
RibLength = (BallOD - LightBodies[FlashIndex][F_GRIPOD])/2;
translate([0,0,BallLength/2])
difference() {
intersection() {
sphere(d=BallOD,$fn=2*BallSides); // basic ball
cube([2*BallOD,2*BallOD,BallLength],center=true); // trim to length
}
translate([0,0,-LightBodies[FlashIndex][F_GRIPOD]])
rotate(180/BallSides)
PolyCyl(LightBodies[FlashIndex][F_GRIPOD],2*BallOD,BallSides); // remove flashlight body
for (i=[0:NumSlots/2 - 1]) { // cut slots
a=i*(2*360/NumSlots);
SlotCutterLength = LightBodies[FlashIndex][F_GRIPOD];
rotate(a)
translate([SlotCutterLength/2,0,SlotBaseThick])
cube([SlotCutterLength,SlotWidth,BallLength],center=true);
rotate(a + 360/NumSlots)
translate([SlotCutterLength/2,0,-SlotBaseThick])
cube([SlotCutterLength,SlotWidth,BallLength],center=true);
}
}
color("Yellow")
if (Support) {
for (i=[0:NumSlots-1]) {
a = i*360/NumSlots;
rotate(a + 180/NumSlots)
translate([(LightBodies[FlashIndex][F_GRIPOD] + RibLength)/2 + ThreadWidth,0,BallLength/(2*4)])
cube([RibLength,2*ThreadWidth,BallLength/4],center=true);
}
}
}
//- Clamp around flashlight ball
module BallClamp() {
BossLength = ClampScrew[LENGTH] - 1*ClampScrewWasher[LENGTH];
BossOD = ClampInsert[OD] + 2*(6*ThreadWidth);
difference() {
union() {
intersection() {
sphere(d=ClampOD,$fn=BallSides); // exterior ball clamp
cube([ClampLength,2*ClampOD,2*ClampOD],center=true); // aiming allowance
}
hull()
for (j=[-1,1])
translate([0,j*ClampScrewOC/2,-BossLength/2])
cylinder(d=BossOD,h=BossLength,$fn=6);
}
sphere(d=(BallOD + 1*ThreadThick),$fn=BallSides); // interior ball with minimal clearance
for (j=[-1,1]) {
translate([0,j*ClampScrewOC/2,-ClampOD]) // screw clearance
PolyCyl(ClampScrew[ID],2*ClampOD,6);
translate([0,j*ClampScrewOC/2, // insert clearance
-(BossLength/2 - ClampInsert[LENGTH] - 3*ThreadThick)])
rotate([0,180,0])
PolyCyl(ClampInsert[OD],2*ClampOD,6);
translate([0,j*ClampScrewOC/2, // insert transition
-(BossLength/2 - ClampInsert[LENGTH] - 3*ThreadThick)])
cylinder(d1=ClampInsert[OD]/cos(180/6),d2=ClampScrew[ID],h=6*ThreadThick,$fn=6);
}
}
color("Yellow")
if (Support) { // ad-hoc supports for top half
NumRibs = 6;
RibLength = 0.5 * BallOD;
RibWidth = 1.9*ThreadWidth;
SupportOC = ClampLength / NumRibs;
cube([ClampLength,RibLength,4*ThreadThick],center=true); // base plate for adhesion
render(convexity=2*NumRibs)
intersection() {
sphere(d=BallOD - 0*ThreadWidth); // cut at inner sphere OD
cube([ClampLength + 2*ThreadWidth,RibLength,BallOD],center=true);
union() { // ribs for E-Z build
for (j=[-1,0,1])
translate([0,j*SupportOC,0])
cube([ClampLength,RibWidth,1.0*BallOD],center=true);
for (i=[0:NumRibs]) // allow NumRibs + 1 to fill the far end
translate([i*SupportOC - ClampLength/2,0,0])
rotate([0,90,0])
cylinder(d=BallOD - 2*ThreadThick,
h=RibWidth,$fn=BallSides,center=true);
}
}
}
}
//- Mount between fairing plate and flashlight ball
// Build with support for bottom of clamp screws!
module Mount() {
difference() {
translate([-BracketHoleOC,0,0]) // put bracket center at origin
PlateBlank();
mirror([0,1,0])
translate([0,0,-Protrusion])
linear_extrude(height=3*ThreadThick + Protrusion) {
translate([BracketHoleOC + 15,0,0])
text(text=">>>",size=5,spacing=1.20,font="Arial",halign="center",valign="center");
translate([-BracketHoleOC,8,0])
text(text=str("Toe ",ToeIn),size=5,spacing=1.20,font="Arial",halign="center",valign="center");
translate([-BracketHoleOC,-8,0])
text(text=str("Tilt ",Tilt),size=5,spacing=1.20,font="Arial",halign="center",valign="center");
translate([BracketHoleOC,8,0])
text(text=str("Roll ",Roll),size=5,spacing=1.20,font="Arial",halign="center",valign="center");
translate([-(BracketHoleOC + 15),0,0])
rotate(90)
text(text="KE4ZNU",size=4,spacing=1.20,font="Arial",halign="center",valign="center");
}
}
rotate([0,ToeIn,Tilt])
translate([0,0,ClampOD/2])
rotate([-Roll,0,0])
intersection() {
translate([0,0,-ClampOD/2])
cube([2*ClampOD,2*ClampOD,ClampOD],center=true);
BallClamp();
}
color("Yellow")
if (MountSupport) { // anchor outer corners at worst overhang
RibWidth = 1.9*ThreadWidth;
SupportOC = 0.1 * ClampLength;
difference() {
rotate([0,0,Tilt])
translate([Shift,0,0])
for (i=[-4.5,-2.5,0,2.0,4.5])
translate([i*SupportOC - 0.0,0,(5 + Plate[2])/2])
cube([RibWidth,0.7*ClampOD,(5 + Plate[2])],center=true);
rotate([0,ToeIn,Tilt])
translate([Shift,0,ClampOD/2])
rotate([-Roll,0,0])
sphere(d=ClampOD - 2*ThreadWidth,$fn=BallSides);
}
}
}
//- Build things
if (Component == "Ball")
SlotBall();
if (Component == "BallClamp")
if (Layout == "Show")
BallClamp();
else if (Layout == "Build") {
Both = false;
difference() {
union() {
translate([Both ? ClampLength : 0,0,0])
BallClamp();
if (Both)
translate([-ClampLength,0,0])
rotate([180,0,0])
BallClamp();
}
translate([0,0,-ClampOD/2])
cube([2*ClampOD,2*ClampOD,ClampOD],center=true);
}
}
if (Component == "Mount")
Mount();
if (Component == "Plates") {
translate([0,0.7*Plate[1],0])
InnerPlate();
translate([0,-0.7*Plate[1],0])
PlateBlank();
}
if (Component == "Bracket")
Bracket();