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.

Category: Machine Shop

Mechanical widgetry

  • Presta Valve to Schraeder Hole Adapter

    The front rim on my Tour Easy developed a distinct bulge, of the sort usually caused by ramming something, but I’m not Danny McAskell and the bulge got worse over the course of a few weeks, suggesting the rim was deforming under tire pressure. Having ridden it upwards of 35 k miles with plenty of trailer towing and too much crushed-stone trail riding, the brake tracks were badly worn and it’s time for a new rim.

    An Amazon seller had an identical (!) rim, except for the minor difference of having a hole sized for a Schraeder valve stem, rather than the Presta valves on the original rims. One can buy adapters / grommets, but what’s the fun in that?

    The brake track walls are 1.5 mm thick on the new rim and a scant 1.0 mm on the old rim, so, yeah, it’s worn.

    A few measurements to get started (and for future reference):

    Presta to Schraeder Adapter - dimension doodle
    Presta to Schraeder Adapter – dimension doodle

    If you don’t have an A drill, a 15/64 inch drill is only half a mil larger and, sheesh, anything close will be fine.

    Introduce a suitable brass rod to Mr Lathe:

    Presta-Schraeder Adapter - parting off
    Presta-Schraeder Adapter – parting off

    Break all the edges and drop it in place:

    Presta-Schraeder Adapter - installed
    Presta-Schraeder Adapter – installed

    One could argue for swaging the adapter to fit flush against the curved rim, but commercial adapters don’t bother with such refinements and neither shall I.

    The 7.0 mm length got shortened to fit flush with the center of the rim:

    Presta-Schraeder Adapter - valve stem installed
    Presta-Schraeder Adapter – valve stem installed

    It’s brass, because the rim is heaviest on the far side where the steel pins splicing the ends live, and, with the tube & tire installed, the rim came out almost perfectly balanced. Which makes essentially no difference whatsoever, of course.

    The shiny new rim sports shiny new reflector tape (from the same stockpile, of course).

    That was easy …

  • Monthly Science: Cheese Slicer Epoxy Coating vs. Water

    Ten months ago, I cleaned the corrosion off our favorite cheese slicer:

    Cheese slicer - aluminum corrosion
    Cheese slicer – aluminum corrosion

    After cleaning, I coated it with XTC-3D epoxy:

    Cheese Slicer - epoxy coat - detail
    Cheese Slicer – epoxy coat – detail

    We’ve been using it daily ever since and it spends most of its life drip-drying in the dish drainer. I added a third opening to the cheerful orange measuring spoon holder just for the slicer.

    A few weeks ago I noticed corrosion once again growing on the handle:

    Cheese Slicer - epoxy coat - corrosion - detail
    Cheese Slicer – epoxy coat – corrosion – detail

    I think the rot comes from water diffusing through the epoxy, rather than gross leaks through damage or pinholes. The tip of the handle has the most corrosion, probably due to the water drop hanging there, even though it also has the thickest epoxy coating: it cured with the handle pointing downward.

    Verily, rust never sleeps …

  • Bathroom Sink Drain: Epoxy Touchup

    The glaring white ring around the drain comes from Magic Porcelain Chip Fix epoxy:

    Bathroom Sink Drain - Epoxy fill
    Bathroom Sink Drain – Epoxy fill

    What looks like a blob on the left side covers the missing chip, with the rest of the ring filled in to make it look like I knew what I was doing. The drain dried out while we were on vacation, having been scrubbed clean before we left, making for the best surface preparation I could provide.

    As it turns out, our resident iron bacteria took about a week to set up shop along the bottom of the ring, producing a pair of small rust-colored dots that will inevitably spread to encompass the whole thing. They’re endemic in the plumbing, impossible to kill off, and nothing more than an unsightly nuisance.

  • Tour Easy Daytime Running Light: 18650 Cell Extraction Tab

    The running lights on our Tour Easy fairing sit just about perfectly level, despite how they appear in relation to the fairing edge:

    Flashlight Mount - LC40 - finger ball - side
    Flashlight Mount – LC40 – finger ball – side

    And, because they’re firmly attached to the fairing mount, there’s no way to tilt them to extract the 18650 cell.

    This took entirely too long to figure out:

    Lithium 18650 Cell Extractor Tab
    Lithium 18650 Cell Extractor Tab

    The LC40 end caps have a recess exactly where it’ll do the most good: capturing the tab inside the cap means it can’t interfere with the rear contact spring:

    Lithium 18650 Cell Extractor Tab - Anker LC40
    Lithium 18650 Cell Extractor Tab – Anker LC40

    Swapping cells no longer requires muttering!

  • Rubber Soaker Hose Repair

    A soaker hose leaped under a descending garden fork and accumulated a nasty gash:

    Soaker Hose Splice - gashed
    Soaker Hose Splice – gashed

    Mary deployed a spare and continued the mission, while I pondered how to fix such an odd shape.

    For lack of anything smarter, I decided to put a form-fitting clamp around the hose, with silicone caulk buttered around the gash to (ideally) slow down any leakage:

    Soaker Hose Splice - Solid Model - Assembled
    Soaker Hose Splice – Solid Model – Assembled

    As usual, some doodling got the solid model started:

    Soaker Hose Splice - Dimension doodle 1
    Soaker Hose Splice – Dimension doodle 1

    A hose formed from chopped rubber doesn’t really have consistent dimensions, so I set up the model to spit out small test pieces:

    Soaker Hose Splice - Test Fit - Slic3r
    Soaker Hose Splice – Test Fit – Slic3r

    Lots and lots of test pieces:

    Soaker Hose Splice - test pieces
    Soaker Hose Splice – test pieces

    Each iteration produced a better fit, although the dimensions never really converged:

    Soaker Hose Splice - Dimension doodle 2
    Soaker Hose Splice – Dimension doodle 2

    The overall model looks about like you’d expect:

    Soaker Hose Splice - Complete - Slic3r
    Soaker Hose Splice – Complete – Slic3r

    The clamp must hold its shape around a hose carrying 100 psi (for real!) water, so I put 100 mil aluminum backing plates on either side. Were you doing this for real, you’d shape the plates with a CNC mill, but I just bandsawed them to about the right size and transfer-punched the hole positions:

    Soaker Hose Splice - plate transfer punch
    Soaker Hose Splice – plate transfer punch

    Some drill press action with a slightly oversize drill compensated for any misalignment and Mr Disk Sander rounded the corners to match the plastic block:

    Soaker Hose Splice - plate corner rounding
    Soaker Hose Splice – plate corner rounding

    A handful of stainless steel 8-32 screws holds the whole mess together:

    Soaker Hose Splice - installed
    Soaker Hose Splice – installed

    These hoses spend their lives at rest under a layer of mulch, so I’m ignoring the entire problem of stress relief at those sharp block edges. We’ll see how this plays out in real life, probably next year.

    I haven’t tested it under pressure, but it sure looks capable!

    The OpenSCAD source code as a GitHub Gist:

    // Rubber Soaker Hose Splice
    // Ed Nisley KE4ZNU July 2018
    Layout = "Build"; // Hose Block Show Build
    TestFit = false; // true to build test fit slice from center
    //- Extrusion parameters must match reality!
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    inch = 25.4;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    //———-
    // Dimensions
    // Hose lies along X axis
    Hose = [200,27.0,12.0]; // X = longer than anything else
    Block = [80.0,50.0,4.0 + Hose.z]; // overall splice block size
    echo(str("Block: ",Block));
    Kerf = 0.1; // cut through middle to apply compression
    ID = 0;
    OD = 1;
    LENGTH = 2;
    // 8-32 stainless screws
    Screw = [4.1,8.0,3.0]; // OD = head LENGTH = head thickness
    Washer = [4.4,9.5,1.0];
    Nut = [4.1,9.7,6.0];
    CornerRadius = Washer[OD]/2;
    NumScrews = 3; // screws along each side of cable
    ScrewOC = [(Block.x – 2*CornerRadius) / (NumScrews – 1),
    Block.y – 2*CornerRadius,
    2*Block.z // ensure complete holes
    ];
    echo(str("Screw OC: x=",ScrewOC.x," y=",ScrewOC.y));
    //———————-
    // Useful routines
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(d=(FixDia + HoleWindage),h=Height,$fn=Sides);
    }
    // Hose shape
    // This includes magic numbers measured from reality
    module HoseProfile() {
    RimThick = 10.0; // outer sections
    RimOD = RimThick;
    RimFlatRecess = -0.7; // recess to front flat surface
    OuterOC = Hose.y – RimOD; // outer tube centers
    RecessM = 1.5; // back recess chord
    RecessC = OuterOC;
    RecessR = (pow(RecessM,2) + pow(RecessC,2)/4) / (2*RecessM);
    RidgeM = 1.0; // front ridge chord
    RidgeC = 8.0;
    RidgeR = (pow(RidgeM,2) + pow(RidgeC,2)/4) / (2*RidgeM);
    NumSides = 12*4;
    rotate([0,-90,0])
    translate([0,0,-Hose.x/2])
    linear_extrude(height=Hose.x,convexity=4)
    difference() {
    union() {
    for (j=[-1,1]) // outer channels
    translate([0,j*OuterOC/2])
    circle(d=RimOD,$fn=NumSides);
    translate([-RimOD/4,0]) // rear flat fill
    square([RimOD/2,OuterOC],center=true);
    translate([(RimOD/4 + RimFlatRecess),0]) // front flat fill
    square([RimOD/2,OuterOC],center=true);
    intersection() {
    translate([Hose.z/2,0])
    square([Hose.z,OuterOC],center=true);
    translate([-RidgeR + RimOD/2 + RimFlatRecess + RidgeM,0])
    circle(r=RidgeR,$fn=NumSides);
    }
    }
    translate([-(RecessR + RimOD/2 – RecessM),0])
    circle(r=RecessR,$fn=2*NumSides);
    }
    }
    // Outside shape of splice Block
    // Z centered on hose rim circles, not overall thickness through center ridge
    module SpliceBlock() {
    difference() {
    hull()
    for (i=[-1,1], j=[-1,1]) // rounded block
    translate([i*(Block.x/2 – CornerRadius),j*(Block.y/2 – CornerRadius),-Block.z/2])
    cylinder(r=CornerRadius,h=Block.z,$fn=4*8);
    for (i = [0:NumScrews – 1], j=[-1,1]) // screw holes
    translate([-(Block.x/2 – CornerRadius) + i*ScrewOC.x,
    j*ScrewOC.y/2,
    -(Block.z/2 + Protrusion)])
    PolyCyl(Screw[ID],Block.z + 2*Protrusion,6);
    cube([2*Block.x,2*Block.y,Kerf],center=true); // slice through center
    }
    }
    // Splice block less hose
    module ShapedBlock() {
    difference() {
    SpliceBlock();
    HoseProfile();
    }
    }
    //———-
    // Build them
    if (Layout == "Hose")
    HoseProfile();
    if (Layout == "Block")
    SpliceBlock();
    if (Layout == "Bottom")
    BottomPlate();
    if (Layout == "Top")
    TopPlate();
    if (Layout == "Show") {
    difference() {
    SpliceBlock();
    HoseProfile();
    }
    color("Green",0.25)
    HoseProfile();
    }
    if (Layout == "Build") {
    SliceOffset = TestFit && !NumScrews%2 ? ScrewOC.x/2 : 0;
    intersection() {
    translate([SliceOffset,0,Block.z/4])
    if (TestFit)
    cube([ScrewOC.x/2,4*Block.y,Block.z/2],center=true);
    else
    cube([4*Block.x,4*Block.y,Block.z/2],center=true);
    union() {
    translate([0,0.6*Block.y,Block.z/2])
    ShapedBlock();
    translate([0,-0.6*Block.y,Block.z/2])
    rotate([0,180,0])
    ShapedBlock();
    }
    }
    }
  • Tour Easy Front Fender Clip: Longer and Stronger

    We negotiated the Belmar Bridge connection stairway from the Allegheny River Trail to the Sandy Creek trail:

    Belmar Bridge Stairs - Overview
    Belmar Bridge Stairs – Overview

    We’re maneuvering Mary’s bike, but you get the general idea. Our bikes aren’t built for stairways, particularly ones with low overheads:

    Belmar Bridge Stairs - Low Overhead
    Belmar Bridge Stairs – Low Overhead

    The front fender clip on my Tour Easy snapped (at the expected spots) when the mudflap snagged on one of the angles:

    Belmar Bridge Stairs - First Turn
    Belmar Bridge Stairs – First Turn

    For some inexplicable reason, I didn’t have a roll of duct tape in my packs, so the temporary repair required a strip of tape from a battery pack, two snippets of hook-and-loop tape, and considerable muttering:

    Tour Easy front fender clip - expedient repair
    Tour Easy front fender clip – expedient repair

    It was good for two dozen more miles to the end of our vacation, so I’d say that was Good Enough.

    The new version has holes in the ferrules ten stay diameters deep, instead of six, which might eliminate the need for heatstink tubing. I added a small hole at the joint between the curved hooks and the ferrules to force more plastic into those spots:

    Front Fender Clip - Slic3r
    Front Fender Clip – Slic3r

    I also bent the hanger extension to put the fender’s neutral position closer to the wheel.

    We’ll see how long this one lasts. By now, I now have black double-sticky foam tape!

    The OpenSCAD source code as a GitHub Gist:

    // Tour Easy front fender clip
    // Ed Nisley KE4ZNU July 2017
    Layout = "Build"; // Build Profile Ferrule Clip
    //- Extrusion parameters must match reality!
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    inch = 25.4;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    //———————-
    // Dimensions
    // special case: fender is exactly half a circle!
    FenderC = 51.0; // fender outside width = chord
    FenderM = 21.0; // height of chord
    FenderR = (pow(FenderM,2) + pow(FenderC,2)/4) / (2 * FenderM); // radius
    echo(str("Fender radius: ", FenderR));
    FenderD = 2*FenderR;
    FenderA = 2 * asin(FenderC / (2*FenderR));
    echo(str(" … Arc: ",FenderA," deg"));
    FenderThick = 2.5; // fender thickness, assume dia of edge
    ClipHeight = 15.0; // top to bottom, ignoring rakish tilt
    ClipThick = IntegerMultiple(2.5,ThreadWidth); // thickness of clip around fender
    ClipD = FenderD; // ID of clip against fender
    ClipSides = 4 * 8; // polygon sides around clip circle
    BendReliefD = 2.5; // bend arch diameter
    BendReliefA = 2/3 * FenderA/2; // … angle from dead ahead
    BendReliefCut = 1.5; // factor to thin outside of bend
    ID = 0;
    OD = 1;
    LENGTH = 2;
    StayDia = 3.3; // fender stay rod diameter
    StayOffset = 15.0; // stay-to-fender distance
    StayPitch = -5; // angle from stay to fender arch
    DropoutSpace = 120; // stay spacing at wheel hub
    StayLength = 235; // stay length: hub to fender
    StaySplay = asin((DropoutSpace – FenderC)/(2*StayLength)); // outward angle to hub
    echo(str(" … Pitch: ",StayPitch," deg"));
    echo(str(" … Splay: ",StaySplay," deg"));
    FerruleSides = 2*4;
    Ferrule = [StayDia,3*FenderThick/cos(180/FerruleSides),10*StayDia + StayOffset]; // ID = stay rod OD
    FerruleHoleD = 0.1; // small hole to create solid plastic at ferrule joint
    //———————-
    // Useful routines
    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);
    }
    //———————-
    // Clip profile around fender
    // Centered on fender arc
    module Profile(HeightScale = 1) {
    linear_extrude(height=HeightScale*ClipHeight,convexity=5) {
    difference() {
    offset(r=ClipThick) // outside of clip
    union() {
    circle(d=ClipD,$fn=ClipSides);
    for (i=[-1,1])
    rotate(i*BendReliefA) {
    translate([ClipD/2 + BendReliefD/2,0,0])
    circle(d=BendReliefD,$fn=6);
    }
    }
    union() { // inside of clip
    circle(d=ClipD,$fn=ClipSides);
    for (i=[-1,1])
    rotate(i*BendReliefA) {
    translate([ClipD/2 + BendReliefCut*BendReliefD/2,0,0])
    circle(d=BendReliefD/cos(180/6),$fn=6);
    translate([ClipD/2,0,0])
    square([BendReliefCut*BendReliefD,BendReliefD],center=true);
    }
    }
    translate([(FenderR – FenderM – FenderD/2),0]) // trim ends
    square([FenderD,2*FenderD],center=true);
    }
    for (a=[-1,1]) // hooks around fender
    rotate(a*(FenderA/2))
    translate([FenderR – FenderThick/2,0]) {
    difference() {
    rotate(1*180/12)
    circle(d=FenderThick + 2*ClipThick,$fn=12);
    rotate(1*180/8)
    circle(d=FenderThick,$fn=8);
    rotate(a * -90)
    translate([0,-2*FenderThick,0])
    square(4*FenderThick,center=false);
    }
    }
    }
    }
    //———————-
    // Ferrule body
    module FerruleBody() {
    translate([0,0,Ferrule[OD]/2 * cos(180/FerruleSides)])
    rotate([0,-90,0]) rotate(180/FerruleSides)
    difference() {
    cylinder(d=Ferrule[OD],h=Ferrule[LENGTH],$fn=FerruleSides,center=false);
    translate([0,0,StayOffset + Protrusion])
    PolyCyl(Ferrule[ID],Ferrule[LENGTH] – StayOffset + Protrusion,FerruleSides);
    }
    }
    //———————-
    // Generate entire clip at mounting angle
    module FenderClip() {
    difference() {
    union() {
    translate([FenderR,0,0])
    difference() { // angle and trim clip
    rotate([0,StayPitch,0])
    translate([-(FenderR + ClipThick),0,0])
    Profile(2); // scale upward for trimming
    translate([0,0,-ClipHeight]) // trim bottom
    cube(2*[FenderD,FenderD,ClipHeight],center=true);
    translate([0,0,ClipHeight*cos(StayPitch)+ClipHeight]) // trim top
    cube(2*[FenderD,FenderD,ClipHeight],center=true);
    }
    for (j = [-1,1]) // place ferrules
    translate([Ferrule[OD]*sin(StayPitch) + (Ferrule[OD]/2)*sin(StaySplay),j*(FenderR – FenderThick/2),0])
    rotate(-j*StaySplay)
    FerruleBody();
    }
    for (i=[-1,1]) // punch stiffening holes
    translate([FenderThick/2,-i*(FenderR – FenderThick/2),Ferrule[OD]/2])
    rotate([0,-90,i*StaySplay])
    PolyCyl(FerruleHoleD,Ferrule[OD],FerruleSides);
    }
    }
    //———————-
    // Build it
    if (Layout == "Profile") {
    Profile();
    }
    if (Layout == "Ferrule") {
    FerruleBody();
    }
    if (Layout == "Clip") {
    FenderClip();
    }
    if (Layout == "Build") {
    FenderClip();
    }

    As a bonus for paging all the way to the end, here’s the descent on the same stairway:

    Belmar Bridge Stairs - Descent
    Belmar Bridge Stairs – Descent

    No, I wasn’t even tempted …

  • Anker LC40 Flashlight: Anodizing Fade

    The top surface of the Anker LC40 flashlight serving as the daytime running light on Mary’s bike sees plenty of sunlight, particularly when it’s sitting beside her garden plots, and the black anodized finish on the screw-in battery cap has begun fading:

    Anker LC40 Flashlight - Anodizing fade
    Anker LC40 Flashlight – Anodizing fade

    The bottom side of the cap is in fine shape, as is the main case, so the two parts came from different metal finishing lines.

    The light on my bike, a marginally newer and essentially identical Bolder LC40, remains all black. I have no idea what “Bolder” means in this context.

    Obviously, I must get out more …