Planet Bike Superflash: Tour Easy Mount

Having not yet gotten around to building better taillights for our bikes, we picked up some Planet Bike Superflash lights on sale. I don’t like single-LED lights, because the optics produce a concentrated beam (which is how they get such high lumen ratings) that’s essentially invisible anywhere off-axis; a taillight that requires careful alignment for maximum effect is a Bad Thing. But, eh, they were on sale…

The graceful OEM seatpost mount, done in engineering plastic with smooth curves and something of a reputation for fragility, doesn’t work on a recumbent, so I build a butt-ugly mount that should last forever. It clamps firmly around a length of grippy silicone tape on the top seat frame rail:

Superflash on Tour Easy
Superflash on Tour Easy

The reviews also complain that normal road vibrations transmitted through the somewhat whippy OEM mount pop the case apart, depositing the lens and electronics on the road behind you. Hence the black tape across the case joint.

Here’s the whole affair on the bench:

Superflash on mount
Superflash on mount

The weird color line comes from white plastic left in the extruder that covers the bottom layer or two of each part. I’m not fussy about the first pass of any new gadget, because I know I’ll build at least one more to get everything right.

This is the first build arrangement; note the huge white teardrop blob at the start of the Skirt outline on the left. Obviously I didn’t have the initial retraction under control:

Superflash mount on build platform
Superflash mount on build platform

The screw recesses built over the plate and got cute little support spiders to keep their interiors from sagging:

Superflash mount - bolt support
Superflash mount - bolt support

After doing it that way, I flipped the top piece over so it builds with the screw head recesses upward to get a better finish on those nice curves. That means the arch needs support, which almost worked, although some of the fins fell over:

Superflash mount - failed arch support
Superflash mount - failed arch support

The solid model now adds a two-layer-thick flat plate joining the fins that should hold them firmly to the build plate.

Clamp Support - Solid Model
Clamp Support - Solid Model

I also added an option to build the flash mounting shoe separately:

Superflash mount - solid model
Superflash mount - solid model

That gives better control over the flange thickness, which turns out to be critical parameter requiring a bit of adjustment with a file in the first version. Of course, the shoe needs an alignment pin and another assembly step to glue it in place:

Superflash mount - gluing shoe
Superflash mount - gluing shoe

A 4-40 setscrew jams into the latch recess in the Superflash case, thus preventing it from walking off the shoe. You don’t need any particular pressure here, just enough protrusion to engage the case:

Superflash mount - setscrew
Superflash mount - setscrew

The first pass at hex nut recesses were exactly cos(30) too large, as I forgot my Useful Sizes file has the across-the-points diameter, so I added a dab of epoxy to each recess before gluing the halves together with solvent:

Superflash mount - glue clamping
Superflash mount - glue clamping

And then it’s all good.

The OpenSCAD source code:

// Planet Bike Superflash mount for Tour Easy seatback
// Ed Nisley KE4ZNU - Dec 2011

Layout = "Show";            // Assembly: Show
                            // Parts: Clamp Base Shoe Mount
                            // Build Plate: Build

SeparateShoe = true;        // true = print mounting shoe separately
                            // false = join shoe to Mount block

Support = true;             // true = include support

Gap = 8;                    // between "Show" objects

include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
include </home/ed/Thing-O-Matic/Useful Sizes.scad>
include </home/ed/Thing-O-Matic/lib/visibone_colors.scad>

//-------
//- Extrusion parameters must match reality!
//  Print with +1 shells, 3 solid layers, 0.2 infill

ThreadThick = 0.25;
ThreadWidth = 2.0 * ThreadThick;

HoleFinagle = 0.1;
HoleFudge = 1.00;

function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;

Protrusion = 0.1;           // make holes end cleanly

function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
function IntegerMultipleMin(Size,Unit) = Unit * floor(Size / Unit);

//-------
// Dimensions

BarDia = (5/8) * inch;              // seat back rail diameter
BarRad = BarDia/2;

TapeThick = 0.3;                    // grippy tape around bar

HoleDia = BarDia + 2*TapeThick;     // total hole dia
HoleRad = HoleDia/2;
HoleSides = 4*5;

echo("Bar hole dia: ",HoleDia);

TightSpace = 1.0;                   // space for tightening screws

PlateWidth = 20.0;                  // mounting plate across flanges
PlateLength = 20.0;                 //  ... parallel to flanges
PlateThick = IntegerMultipleMin(1.96,ThreadThick);          //  ... thickness
FlangeThick = IntegerMultiple(1.40,ThreadThick);            // lamp flange thickness
FlangeWidth = 2.0;                  //  ... width

ShoeThick = PlateThick + FlangeThick;    // dingus protruding from main block
ShoeOffset = 1.0;                   // offset due to end wall

echo("Shoe thickness: ",ShoeThick," = ",PlateThick," + ",FlangeThick);

LockOffset = -5.0;                  // offset of locking setscrew

TopRoundRad = 1.5*Head10_32/2;      // tidy rounding on top edge of clamp
echo("Top rounding radius: ",TopRoundRad);

NutDia = Nut10_32Dia*cos(30);       // adjust from across-points to across-flats dia
NutPart = IntegerMultiple(0.5*Nut10_32Thick,ThreadThick);  // part of nut in each half

BoltOffset = HoleRad + max(Head10_32,NutDia);
BoltClear = Clear10_32;
BoltHeadDia = Head10_32;
BoltHeadThick = Head10_32Thick;

MountWidth = PlateLength + ShoeOffset;         // side-to-side
MountLength = HoleDia + 3.5*max(BoltHeadDia,NutDia);

ClampHeight = TopRoundRad + HoleRad;            // includes gap/2 for simplicity
BaseHeight = NutPart + HoleRad;                 //  ... likewise
MountHeight = PlateWidth;

echo("Mount width: ",MountWidth," length: ",MountLength);
echo("Height of clamp: ",ClampHeight," base: ",BaseHeight," mount: ",MountHeight);
echo(" total: ",ClampHeight+BaseHeight+MountHeight);

AlignPegDia = 2.9;                  // shoe alignment peg
AlignPegLength = ShoeThick;

echo("Alignment peg length: ",AlignPegLength);

//-------

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=HoleAdjust(FixDia)/2,h=Height,$fn=Sides);
}

module ShowPegGrid(Space = 10.0,Size = 1.0) {

  Range = floor(50 / Space);

    for (x=[-Range:Range])
      for (y=[-Range:Range])
        translate([x*Space,y*Space,Size/2])
          %cube(Size,center=true);
}

//-------
// Upper clamp half

module Clamp() {

  difference() {
    translate([0,0,-TightSpace/2]) {
      difference() {
        translate([0,0,ClampHeight/2]) {
          intersection() {
            translate([0,0,-TopRoundRad])
              minkowski() {
                cube([(MountLength - 2*TopRoundRad),
                      (MountWidth - 2*Protrusion),
                      ClampHeight],center=true);
                rotate([90,0,0])
                  cylinder(r=TopRoundRad,h=Protrusion,$fn=4*8);

              }
            cube([MountLength,MountWidth,ClampHeight],center=true);
          }
        }
        translate([0,(MountWidth/2 + Protrusion)])
          rotate([90,0,0])
            PolyCyl(HoleDia,(MountWidth + 2*Protrusion),HoleSides);
        for (Index=[-1,1])
          translate([(Index*BoltOffset),0,0]) {
            translate([0,0,-Protrusion])
              PolyCyl(BoltClear,(ClampHeight + Protrusion));
            translate([0,0,(ClampHeight - BoltHeadThick)])
              PolyCyl(BoltHeadDia,(BoltHeadThick + Protrusion));
          }
      }

    }
    translate([0,0,-TightSpace/2])
      cube([(MountLength + 2*Protrusion),
           (MountWidth + 2*Protrusion),
           TightSpace],center=true);
  }

    if (Support)                    // choose support to suit printing orientation
      union() {
        translate([0,0,1.5*ThreadThick])
          cube([0.75*HoleDia,(MountWidth + 2*ThreadWidth),3*ThreadThick],center=true);
        intersection() {
          for (Index=[-3:3])
            translate([0,Index*(MountWidth/6),-TightSpace/2])
              rotate([90,0,0])
                cylinder(r=(HoleRad - 0.25*ThreadThick),
                        h=2*ThreadWidth,center=true,$fn=HoleSides);
          translate([-HoleRad,-MountWidth,0])
            cube([HoleDia,2*MountWidth,HoleRad]);
        }
      }
}

//-------
// Lower clamp half = base

module Base() {

  difference() {
    translate([0,0,-TightSpace/2])
      difference() {
        translate([0,0,BaseHeight/2])
          cube([MountLength,MountWidth,BaseHeight],center=true);
        translate([0,(MountWidth/2 + Protrusion)])
          rotate([90,0,0])
            PolyCyl(HoleDia,(MountWidth + 2*Protrusion),HoleSides);
        for (Index=[-1,1])
          translate([(Index*BoltOffset),0,0]) {
            translate([0,0,-Protrusion])
              PolyCyl(BoltClear,(BaseHeight + Protrusion));
            translate([0,0,(BaseHeight - NutPart)])
              rotate(30)
                PolyCyl(NutDia,(NutPart + Protrusion),6);
//              cylinder(r=NutDia/2,h=(NutPart + Protrusion),$fn=6);
          }
      }
    translate([0,0,-TightSpace/2])
      cube([(MountLength + 2*Protrusion),
           (MountWidth + 2*Protrusion),
           TightSpace],center=true);

  }

  if (Support)
    for (Index=[-1,1])                  // support inside nut openings
      translate([(Index*BoltOffset),
                0,
                (BaseHeight - (NutPart - ThreadThick) - TightSpace/2)]) {
        translate([0,0,0])
          for (Seg=[0:5]) {
            rotate(30 + 360*Seg/6)
              cube([NutDia/2,2*ThreadWidth,NutPart - ThreadThick],center=false);
          }
      }

}

//-------
// Superflash mounting shoe
// Offset by -ShoeOffset/2 in Y to align on Mount (half of total offset on each side)

module Shoe() {

    difference() {
      translate([-ShoeThick/2,-ShoeOffset/2,PlateWidth/2])
        if (SeparateShoe)
          cube([ShoeThick,PlateLength,PlateWidth],center=true);
        else
          cube([(ShoeThick + Protrusion),PlateLength,PlateWidth],center=true);

      translate([-(FlangeThick - Protrusion),
                -(PlateLength/2 + ShoeOffset/2 + Protrusion),
                (MountHeight - FlangeWidth)])
        cube([FlangeThick,(PlateLength + 2*Protrusion),(FlangeWidth + Protrusion)]);

      translate([-(FlangeThick - Protrusion),
                -(PlateLength/2 + ShoeOffset/2 + Protrusion),
                -Protrusion])
        cube([FlangeThick,(PlateLength + 2*Protrusion),(FlangeWidth + Protrusion)]);

      translate([-(ShoeThick + Protrusion),LockOffset,MountHeight/2])
        rotate([0,90,0])
          rotate(0)                 // align to match Mount hole orientation
            PolyCyl(Tap4_40,(ShoeThick + 2*Protrusion));

      if (SeparateShoe)
        translate([-(ShoeThick - AlignPegLength/2),0,MountHeight/2])
          rotate([0,90,0])
            PolyCyl(AlignPegDia,AlignPegLength);
    }
}

//-------
// Bottom block for Superflash mount

module Mount() {

  translate([0,0,MountHeight/2])
    union() {
      difference() {
        union() {
          translate([-MountLength/4,0,0])
            cube([MountLength/2,MountWidth,MountHeight],center=true);
          translate([((MountLength/2 - MountHeight)/2 + Protrusion),0,0])
            cube([(MountLength/2 - MountHeight + 2*Protrusion),
                 MountWidth,
                 MountHeight],center=true);
          translate([(MountLength/2 - MountHeight),0,0])
            intersection() {
              translate([MountLength/4,0,0])
                cube([MountLength/2,MountWidth,MountHeight],center=true);
              translate([0,0,MountHeight/2])
                rotate([90,0,0])
                  cylinder(r=MountHeight,h=MountWidth,center=true,$fn=4*16);
            }
        }

        translate([-(MountLength/2 + Protrusion),LockOffset,0])
          rotate([0,90,0])
            rotate(0)       // align through hole sides with point upward
              PolyCyl(Clear4_40,(MountLength + 2*Protrusion));

        for (Index=[-1,1])
          translate([(Index*BoltOffset),0,0]) {
            translate([0,0,BaseHeight/2])
              PolyCyl(BoltClear,(BaseHeight/2 + Protrusion));
            translate([0,0,(BaseHeight - NutPart)])
              rotate(30)
                PolyCyl(NutDia,(NutPart + Protrusion),6);
          }

        if (SeparateShoe)
          translate([-(MountLength/2 + AlignPegLength/2),0,0])
            rotate([0,90,0])
              PolyCyl(AlignPegDia,AlignPegLength);
      }

      if (Support)
        for (Index=[-1,1])            // support inside nut openings
          translate([(Index*BoltOffset),0,(MountHeight/2 - (NutPart - ThreadThick))]) {
            translate([0,0,0])
              for (Seg=[0:5]) {
                rotate(30 + 360*Seg/6)
                  cube([NutDia/2,
                      2*ThreadWidth,
                      (NutPart - ThreadThick)],center=false);
              }
          }

      if (!SeparateShoe)
        translate([-MountLength/2,0,-MountHeight/2])
          Shoe();
    }
}

//-------

ShowPegGrid();

if (Layout == "Clamp")
  Clamp();

if (Layout == "Base")
  Base();

if (Layout == "Shoe")
  Shoe();

if (Layout == "Mount")
  Mount();

if (Layout == "Show") {
  translate([0,0,(BaseHeight + MountHeight + Gap)]) {
    translate([0,0,TightSpace/2 + Gap])
      color(MFG) Clamp();
    translate([0,0,-TightSpace/2])
      rotate([180,0,0])
        color(DHC) Base();
  }
  translate([0,0,0])
    color(LDM) render(convexity=3) Mount();

  if (SeparateShoe)
    translate([-(MountLength/2 + Gap),0,0])
      color(DDM) Shoe();
}

if (Layout == "Build") {
  translate([-15,30,(BaseHeight - TightSpace/2)]) rotate([180,0,0])
    Base();
  translate([-15,00,0]) rotate([0,0,0])
    Clamp();
  if (SeparateShoe)
    translate([20,30,ShoeThick]) rotate([0,-90,180])
      Shoe();
  if (SeparateShoe)
    translate([-15,-30,MountHeight]) rotate([180,0,180])
      Mount();
  else
    translate([-15,-40,MountWidth/2]) rotate([90,0,180])
      Mount();

}

The original doodles, done on a retina-burning yellow scratchpad:

Superflash Mount Doodles
Superflash Mount Doodles