The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Day: January 3, 2012

  • 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