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.

Tag: Improvements

Making the world a better place, one piece at a time

  • Bafang vs. Tour Easy: Chain Guide

    Bafang vs. Tour Easy: Chain Guide

    After adding the Bafang motor to my Tour Easy, the chain has fallen off the chainring a few times, prompting the gap filler between the motor and the chainring spider. That this has never happened to Mary’s essentially identical Tour Easy suggests I have a different shift technique, but adding a chain catcher seemed easier than re-learning shifting:

    Chain Catcher - top view
    Chain Catcher – top view

    It’s more properly called a “chain guide” and is basically a shifter cage minus the mechanism:

    Chain Catcher - side view
    Chain Catcher – side view

    Because the Tour Easy frame has a 25 mm tube where the guide’s clamp expects a minimum 31.8 mm tube, a 3D printed adapter fills the gap:

    Chain Catcher adapter ring - solid model
    Chain Catcher adapter ring – solid model

    The hole is off-center because it seemed like a good idea, although it’s not strictly necessary. The flange helps align the pieces while tightening the clamp screw.

    The guide cage clears the chain on all sides while up on the work stand, but there’s nothing like getting out on the road to find out why something doesn’t work as you expect.

    The OpenSCAD source code as a GitHub Gist:

    // Chain catcher adapter ring
    // Ed Nisley – KE4ZNU – 2023-05
    /* [Hidden] */
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    ID = 0;
    OD = 1;
    LENGTH = 2;
    inch = 25.4;
    //———————-
    // Dimensions
    TubeOD = 26.0; // frame tube with silicone tape
    Clamp = [35.0,39.0,12.0]; // Chain catcher clamp ring
    Flange = [Clamp[ID],Clamp[OD],3*ThreadThick];
    Kerf = 1.0;
    Offset = (Clamp[ID] – TubeOD)/2 – 3*ThreadWidth;
    NumSides = 2*3*4;
    //———————–
    $fn=NumSides;
    difference() {
    union() {
    cylinder(d=Flange[OD],h=Flange[LENGTH]);
    cylinder(d=Clamp[ID],h=Clamp[LENGTH]+Flange[LENGTH]);
    }
    cube([2*Flange[OD],Kerf,3*Clamp[LENGTH]],center=true);
    translate([0,Offset,0])
    cylinder(d=TubeOD,h=3*Clamp[LENGTH],center=true);
    }

  • Bafang Motor: Chain Gap Filler

    Bafang Motor: Chain Gap Filler

    When the chain falls off the top of the chainring toward the motor, the part remaining engaged with the chainring will inevitably drag the rest into the gap between the motor and the chainring spider, whereupon it will jam firmly in place and be almost impossible to extract. Preventing this means filling the gap, which required several iterations:

    Bafang motor gap filler - prototypes
    Bafang motor gap filler – prototypes

    The Bafang motor has a cover held in place by seven M3 flat-head screws, shown here below a test filler using pan head screws:

    Bafang motor gap filler - installed
    Bafang motor gap filler – installed

    Contrary to what you might think, the five screws that obviously sit on five points of a hexagon do not in fact sit 60° apart. How you find this out is by making the obvious layout, including the two screws bracketing the pinion gear in the lower right, then applying windage:

    Bafang motor housing gap filler - hole adjustments
    Bafang motor housing gap filler – hole adjustments

    That’s one of the paper templates seen above, with laser-cut holes 60° apart and ugly holes punched at the actual screw locations. Then you scan and overlay that image with the LightBurn layout and twiddle the hole locations to make the answer come out right:

    Bafang motor housing gap filler - hole adjustments - LB overlay
    Bafang motor housing gap filler – hole adjustments – LB overlay

    With that in hand, I cut a 1 mm acrylic shape to measure the clearance between the motor + filler and the chainring spider, with pan-head screws replacing the original flat-head screws:

    Bafang motor gap filler - top view
    Bafang motor gap filler – top view

    That’s a single piece of 2.5 mm acrylic I used after discovering a pair of the 1 mm acrylic shapes fit with space to spare: hooray for rapid prototyping.

    A test chain drop suggested it might suffice:

    Bafang motor gap filler - test
    Bafang motor gap filler – test

    If I were so inclined, 3 mm acrylic with countersunk holes and slightly longer flat-head screws would probably work, but I’ll use this until it fails to prevent a chain snag.

    The careful observer will have noted the stress crack extending radially inward from the upper-right screw, which I am carefully avoiding doing anything about, pending the aforementioned failure.

    The LightBurn layout as a GitHub Gist:

    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.

  • Tour Easy Running Light: Heatsink Machining

    Tour Easy Running Light: Heatsink Machining

    Having acquired some thick-wall (1 inch OD, ¾ inch ID) aluminum tube, making the LED heatsink and lens holder for a running light generates a lot less scrap. A new doodle gives the dimensions in a rather Picasso-ish layout:

    Running Light - dimension doodles
    Running Light – dimension doodles

    The back end of the tube gets turned down to 23 mm OD and cleaned up to 19 mm ID, then scored to give the epoxy something to grip:

    Front Running Light - Heatsink shell scoring
    Front Running Light – Heatsink shell scoring

    The front end gets bored to 22.5 mm for the lens holder and has its OD cleaned up to 25 mm:

    Front Running Light - finished shell
    Front Running Light – finished shell

    Clean up the end of a ¾ inch rod to 19 mm OD, knurl it a little to increase the OD ever so slightly and improve its griptivity, slice off a bit more than 10 mm, butter it up with JB Weld epoxy, and shove it into the shell with its front end aligned and its back end sticking out:

    Front Running Light - epoxied plug in shell - rear
    Front Running Light – epoxied plug in shell – rear

    Face off the back end and the front end looks fine as assembled:

    Front Running Light - epoxied plug in shell - front
    Front Running Light – epoxied plug in shell – front

    Grab it in the Sherline mill’s three jaw chuck to:

    • Drill & tap the M3 central hole for the stud holding the circuit plate to the back end
    • Drill 1.6 mm blind holes for the circuit plate pins
    • Drill 2 mm through holes for the LED wires, 60° apart

    Which looks like this from the front:

    Front Running Light - drilled heatsink - front
    Front Running Light – drilled heatsink – front

    And like this with the circuit plate screwed & glued to the rear:

    Front Running Light - circuit plate mounted
    Front Running Light – circuit plate mounted

    Clean up the OD of some ¾ inch PVC pipe to 25 mm, bore it out to 23 mm.

    While the Sherline is set up, drill a pair of 2 mm holes in the lens holder for the wires, aligned so they’ll match the heatsink holes.

    Because we live in the future, laser-cut the rear cap from some edge-lit acrylic with a black inner disk:

    Front Running Light - PVC tube - end cap
    Front Running Light – PVC tube – end cap

    Cutting that cap with the notch included is now trivially easy, compared to the previous machining.

    Now for some circuitry …

  • Kitchen Scale: Button Shield

    Kitchen Scale: Button Shield

    While I was thinking about something else, I added a back shield to our kitchen scale:

    SmartHeart 19-106 Scale - button shield
    SmartHeart 19-106 Scale – button shield

    A pogo pin connected to the circuit common contacts the copper foil when the bottom cover is screwed down:

    SmartHeart 19-106 Scale - shield pogo pin - detal
    SmartHeart 19-106 Scale – shield pogo pin – detal

    The shield prevents the buttons from responding to fingers on the bottom of the scale, so it no longer wakes up when I extract it from the under-cabinet shelf, and concentrates its attention on the buttons, so it no longer seems quite so willing to lock up due to mysterious influences.

    With an absurd amount of rebuilding, this scale is becoming not a complete waste of free money.

    The batteries soaked up 240 mA·hr of charge, which means the scale drew about 10 mA/day over the last three weeks. Given that the scale’s original 2032 lithium cells have a total capacity around 220 ma·hr for currents in the microamp range, expecting them to supply a current around 10 mA is was absurd.

  • Laser Air Assist Pump Snorkel

    Laser Air Assist Pump Snorkel

    Having verified that putting an assist air pump inside the laser cabinet is a Bad Idea™, I set about building a fitting to attach to the air pump’s inlet connection:

    OMTech 60 W Laser Air Assist - pump inlet
    OMTech 60 W Laser Air Assist – pump inlet

    Note that it’s possible to see the inlet, but not do much with it. I think the bottom plate could be pried off those squishy rubber pillars supporting / isolating the pump, but I didn’t see any need to do so.

    The doodle I made at the time gives the dimensions:

    OMTech 60 W Laser Air Assist - pump inlet fitting measurements
    OMTech 60 W Laser Air Assist – pump inlet fitting measurements

    Back then, I thought of 3D printing the fitting, but the fact that the parts had to be 1.5 mm thick suggested laser cutting the parts from acrylic sheet:

    Laser cutter air pump - keyed fitting A - parts
    Laser cutter air pump – keyed fitting A – parts

    The three top disks come from a 3M LSE adhesive sheet and hold the three layers together, with one spare because I know better than to cut exactly as many as I think I’ll need:

    Laser cutter air pump - keyed fitting - assembled
    Laser cutter air pump – keyed fitting – assembled

    The alert reader will note the middle layer in that picture isn’t a simple round disk. After putting the first version together, I realized the keyed bottom layer could continue turning until it fell out, so I added stops to the middle disk:

    Laser cutter air pump - keyed fitting B - parts
    Laser cutter air pump – keyed fitting B – parts

    Those stops came from the bottom layer layout by welding together three copies of the key opening:

    Laser Cutter Air Assist Pump Fitting - LB design layout
    Laser Cutter Air Assist Pump Fitting – LB design layout

    Space three of those shapes around the ring, subtract them from the outer disk (the same size as the keyed layer), weld them to the middle disk (the same size as the previous middle), and the stops appear as if by magic. Gotta love this geometry stuff.

    The same design produced matching adhesive disks that I applied with tweezers, but if you were doing it in production you’d definitely want to apply a sheet of adhesive to a sheet of acrylic and cut them in the same operation.

    Soften the slightly curved PVC tubing with a heat gun, persuade it to become straight, jam a drill bit inside and grab it in the lathe chuck to keep the fitting perpendicular, glob hot-melt glue around the tubing to hold it in place, and let it cool:

    Laser cutter air pump - fitting hose glue
    Laser cutter air pump – fitting hose glue

    Hot melt glue doesn’t adhere well to acrylic, so cut & apply a disk of LSE adhesive between them, because it sticks like … glue … to both substances.

    Mark and step-drill a hole in the bottom of the laser cabinet, install the fitting on the pump, line things up, and it’s ready to screw down:

    Laser cutter air pump - first trial fitting
    Laser cutter air pump – first trial fitting

    Whereupon I discovered the four silicone rubber feet I added to support the pump base plate and keep it from vibrating against the cabinet let the flexy rubber posts supporting the pump extend too far, thus causing the whole pump to rest on the glue around the fitting.

    Well, I can fix that and, while I’m at it, a snippet of fibrous stuff will keep the tube from rattling around:

    Laser cutter air pump - bottom view - assembled
    Laser cutter air pump – bottom view – assembled

    The four clear disks are 3 mm acrylic stuck to the rubbery feet with more LSE adhesive, with rings around the top to keep everything aligned. It may be possible to line up all four of those things while lowering the pump in place, but not for me.

    With all that once again ready to screw down, the blue tube and its fuzzy felt bumper fell right off, taking the pump’s air inlet connector along. Much to my surprise, the pump draws air through a simple 6 mm hole in its bottom plate:

    Laser cutter air pump - without fitting socket
    Laser cutter air pump – without fitting socket

    Now, the reason I went through all these gyrations was because I had examined that connector, decided it was an integral part of the pump, and there was no way for me to get it off without tearing the pump apart or applying brute force.

    Apparently, all the twisting & turning I did while getting the fittings assembled worked the connector’s unthreaded stem loose in its hole, ready to come out with the slightest pull.

    Verily: Hell hath no fury like that of an unjustified assumption.

    So I cut out a simple disk of 4.5 mm acrylic, hot-melt blobbed a 6 mm ID silicone tube into it, stuck it onto the pump with a (punch-cut!) disk of 3M double-sided foam tape, and declared victory:

    Laser cutter air pump - simple disk fitting
    Laser cutter air pump – simple disk fitting

    Fortunately, the step drill I used on the cabinet left a 9.5 mm hole easily passing the silicone tube’s 9 mm OD, so it all fit together just like I knew what I was doing.

    The silicone tubing has a much larger ID than the original plastic fitting, but the assist air flow remains around 10 l/min. That’s down from the 14 l/m when I installed the flowmeter and 12 l/min with the dual-path assist air control plumbing, but didn’t change with all this fiddling, so the real restriction is in all the blue tubing and myriad fittings on the way to the nozzle.

    On the upside, I now know a bit more about small-scale laser cutting and am well-satisfied with the results.

  • Laser Air Assist Pump Filter: Ewww!

    Laser Air Assist Pump Filter: Ewww!

    Nine months ago I packed cheesecloth around the laser’s air assist pump inlet to filter the smog:

    OMTech 60 W Laser Air Assist - cheesecloth filter installed
    OMTech 60 W Laser Air Assist – cheesecloth filter installed

    Which worked better than I expected.

    I think the track on the right side of the lower / outer filter faced the interior of the cabinet and collected some incoming smog:

    Laser cutter assist air filter - 9 months - outer
    Laser cutter assist air filter – 9 months – outer

    The wad packed around the inlet fitting accumulated even more, due to the concentrated air flow through the round opening:

    Laser cutter assist air filter - 9 months - inner
    Laser cutter assist air filter – 9 months – inner

    Thus we can confirm putting the air pump inside the cutting chamber is not a particularly good idea:

    OMTech 60W laser - Z motor - air pump
    OMTech 60W laser – Z motor – air pump

    My initial notion of drilling a hole through the bottom of the cabinet so the pump can breathe relatively clean basement air takes on new urgency …

  • Tour Easy: SJCAM C100+ Mount

    Tour Easy: SJCAM C100+ Mount

    The batteries (which are no longer available) and the control buttons have worn out on the SJCAM M20 camera on the back of my Tour Easy, so a replacement is in order:

    Tour Easy - SJCAM C100 mount - installed
    Tour Easy – SJCAM C100 mount – installed

    It’s an SJCAM C100+ in its waterproof housing, screwed to a block descended from the M20 mount:

    SJCAM C100 Mount - solid model
    SJCAM C100 Mount – solid model

    The C100+ has a non-replaceable lithium pouch battery that may not last for the hour or so we generally ride, but at least this is a starting point for seeing how the thing works.

    The PrusaSlicer preview shows the support structure inside the seat rail arches:

    SJCAM C100 Mount - slicer
    SJCAM C100 Mount – slicer

    That appears under the four central facets of each arch, where I “painted” the support enforcers, because the automagic supports fill the entire arch and are basically impossible to pry off.

    The hole between the ears on the top holds an aluminum tab intended to diffuse the wobble from that tall camera. A laser-cut chipboard template simplified drilling & cutting the tab from an aluminum sheet:

    Tour Easy - SJCAM C100 mount - test fit
    Tour Easy – SJCAM C100 mount – test fit

    The tab and the brass inserts are held in place with JB Weld Plastic Bonder, my new go-to adhesive for such things.

    The camera includes WiFi and the inevitable app lets you download images without opening the case. Because I’ll be charging the camera after each ride, I may as well just haul the whole thing inside, plug it into a USB port, and proceed as before.

    For future reference, the manual details the operating modes:

    SJCAM C100 Manual - Modes
    SJCAM C100 Manual – Modes

    Because the camera powers up with WiFi enabled and I have no plans to communicate with it while riding, the startup sequence will be:

    • Long-press to power on
    • Double-click to disable WiFi
    • Single-click to start recording

    The OpenSCAD source code as a GitHub Gist:

    // SJCAM C100+ Camera Mount for Tour Easy seat back rail
    // Ed Nisley – KE4ZNU
    // 2023-04
    /* [Layout Options] */
    LookAngle = -20; // camera angle, looking backwards = 0°
    Layout = "Show"; // [Show,Build]
    /* [Hidden] */
    ThreadWidth = 0.40;
    ThreadThick = 0.25;
    HoleWindage = 0.2;
    Protrusion = 0.1;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    //—–
    // Dimensions
    ClampScrew = [5.0,10.0,40.0]; // ID=thread OD=washer LENGTH=total
    ClampInsert = [5.0,7.5,10.5]; // brass insert
    MountScrew = [5.0,10.0,23.0]; // ID=thread OD=washer LENGTH=under nut
    MountInsert = [5.0,7.5,10.5]; // ID=screw OD, OD=knurl dia
    EmbossDepth = 2*ThreadThick + Protrusion; // recess depth + Protrusion beyond surface
    DebossHeight = EmbossDepth; // text height + Protrusion into part
    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(ClampOA = ClampOA);
    ClampScrewOC = IntegerMultiple(ClampOA.x – ClampScrew[OD] – 10*ThreadWidth,1.0);
    echo(ClampScrewOC = ClampScrewOC);
    ClampOffset = 5.0; // in case we need more room on top
    ClampRadius = 3.0;
    ClampSides = 8;
    Kerf = 1.0; // slice through the middle
    // center mount blade, Z = depth into block
    MountBlade = [15.0 + 2*HoleWindage,
    3.0 + 2*HoleWindage,
    (ClampOA.z – RailOD + ClampOffset)/2 – 4*ThreadThick + Protrusion];
    echo(MountBlade = MountBlade);
    MountRadius = MountBlade.x / 2;
    MountGap = 9.5; // camera mount gap around center blade
    MountOffset = [0,0,7.0]; // mount hole offset from block surface
    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);
    }
    //—–
    // Clamp
    // Grips seat frame rail
    // Origin at middle of seat rail, X rearward, Y parallel to seat frame rail
    // Block offset raises whole thing
    module Clamp() {
    difference() {
    translate([0,0,ClampOffset]) {
    difference() {
    union() {
    hull() // the main block
    for (i=[-1,1], j=[-1,1], k=[-1,1])
    translate([i*(ClampOA.x – 2*ClampRadius)/2,
    j*(ClampOA.y – 2*ClampRadius)/2,
    k*(ClampOA.z – 2*ClampRadius)/2])
    sphere(r=ClampRadius/cos(180/ClampSides),$fn=ClampSides);
    hull() // camera mount boss
    for (k=[0,1])
    translate([0,0,k*(MountOffset.z) + ClampOA.z/2])
    rotate([0,90,LookAngle + 90]) rotate(180/12)
    cylinder(r=MountRadius,h=MountScrew[LENGTH],center=true,$fn=12);
    }
    for (i=[-1,1]) // clamp inserts
    translate([i*ClampScrewOC/2,0,-(ClampOA.z/2 + Protrusion)])
    rotate(180/6)
    PolyCyl(ClampInsert[OD],ClampInsert[LENGTH],6);
    for (i=[-1,1]) // clamp screw holes
    translate([i*ClampScrewOC/2,0,-ClampOA.z])
    rotate(180/6)
    PolyCyl(ClampScrew[ID],2*ClampOA.z,6);
    translate([0,0,ClampOA.z/2 – (MountBlade.z/2 – Protrusion/2)]) // camera center blade
    rotate(LookAngle)
    cube(MountBlade,center=true);
    rotate(LookAngle + 90) // camera mount boss slot
    translate([0,0,ClampOA.z/2 + 2*MountRadius])
    cube([MountGap,4*MountRadius,4*MountRadius],center=true);
    translate([0,0,ClampOA.z/2 + MountOffset.z]) // camera mount boss hole
    rotate([90,0,LookAngle])
    cylinder(d=MountScrew[ID],h=4*MountGap,center=true,$fn=6);
    translate([0.3*ClampOA.x, // recess for LookAngle legend
    -(ClampOA.y/2 – (EmbossDepth – Protrusion)/2),
    ClampOA.z/4])
    cube([15,EmbossDepth,8],center=true);
    translate([0,0,-ClampOA.z/2 + (EmbossDepth – Protrusion)/2]) // recess for ID legend
    cube([35,10,EmbossDepth],center=true);
    }
    translate([0.3*ClampOA.x, // LookAngle legend
    -ClampOA.y/2 + DebossHeight + Protrusion/2,
    ClampOA.z/4])
    rotate([90,0,00])
    linear_extrude(height=DebossHeight,convexity=20)
    text(text=str(LookAngle),size=6,spacing=1.20,
    font="Arial:style:Bold",halign="center",valign="center");
    translate([0,0,-ClampOA.z/2]) // ID legend
    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");
    }
    cube([2*ClampOA.x,2*ClampOA.y,Kerf],center=true); // split across rail
    rotate([90,0,0]) // seat rail
    cylinder(d=RailOD,h=2*ClampOA.y,$fn=RailSides,center=true);
    }
    }
    //—–
    // Build things
    // Layouts for design & tweaking
    if (Layout == "Show") {
    Clamp();
    color(FadeColor,FadeAlpha)
    rotate([90,0,0])
    cylinder(d=RailOD,h=2*ClampOA.y,$fn=RailSides,center=true);
    }
    // Build layout
    if (Layout == "Build") {
    translate([0,0.7*ClampOA.y,0])
    difference() {
    translate([0,0,-Kerf/2])
    Clamp();
    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();
    translate([0,0,-ClampOA.z])
    cube(2*ClampOA,center=true);
    }
    }