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

  • Doorknob Repair

    The outer doorknob on the kitchen pantry became very loose and sloppy, with the screw holding the inner knob on the shaft remaining snug. Obviously, something else was wrong inside the door.

    A spring clip should retain the outer knob in the escutcheon:

    Doorknob - worn retaining flange - detail
    Doorknob – worn retaining flange – detail

    The flange holding the clip has worn away, letting the clip fall loose. A side view shows the problem:

    Doorknob shaft - worn retaining flange
    Doorknob shaft – worn retaining flange

    Yes, the knob’s chrome plating is in sorry shape after six decades of wear. I’d rather keep using a solid knob, instead of force-fitting some contemporary half-assed / cost-reduced junk into the door.

    Reference: beausage. I say it “beau-sage”, the beauty that comes from usage.

    The shaft consists of three triangular rods, with the setscrew on the inner knob pressing against the smaller rod to lock all three of them in place and eliminate all rattle & play:

    Doorknob shaft - detail
    Doorknob shaft – detail

    A tapered pin (!) locks the three shaft rods into the outer knob:

    Doorknob shaft - tapered pin
    Doorknob shaft – tapered pin

    Some doodling, most of which turned out to be irrelevant, captured the essential dimensions and suggested how to replace the flange:

    Doorknob - dimension doodles
    Doorknob – dimension doodles

    The stock is 11/16 inch O-1 oil-hardening rod, forever to remain unhardened:

    Doorknob - retainer ring boring
    Doorknob – retainer ring boring

    I drilled a few holes to get up to 1/2 inch, the largest drill bit I have and just barely clearing the the boring bar.

    With the hole bored out to fit the end of the knob, cut it off:

    Doorknob - retainer ring cutoff
    Doorknob – retainer ring cutoff

    Trial-fit the ring on the knob with the spring clip:

    Doorknob - retainer trial fit
    Doorknob – retainer trial fit

    Reinstall the shaft, tap in the retaining pin, then epoxy the ring in place with the knob supported from below to eliminate having to fiddle with the spring clip:

    Doorknob - retainer ring epoxy
    Doorknob – retainer ring epoxy

    Add a few dots of oil here & there, reinstall the parts in reverse order, and the knob works perfectly again. Still looks heavily used, of course, but that’s OK.

    They definitely don’t make ’em like that any more …

     

  • MPCNC: Jogging Keypad for bCNC

    The bCNC G-Code sender sends jogging commands to GRBL from an ordinary numeric keypad:

    MPCNC - Jogging keypad
    MPCNC – Jogging keypad

    Unlike the keypads on my streaming radio players, this one requires no configuration at all, because bCNC regards it as just another keyboard input. The catch: you must select any screen element other than a text entry field to have bCNC recognize the keystrokes as “not text”.

    You would get the same results from the numeric keys on the right side of a full-size / 104-key plank. I’m using a small “tenkeyless” keyboard, which means I can put the keypad wherever it’s easiest to reach while tweaking the MPCNC.

    The ÷10 and ×10 keys along the top row alter the step size by factors of ten, which is pretty much what you need: jog to within a big step of the target, drop to the next lower decade, jog a few more times, maybe drop another decade, jog once, and you’re as close as you need to be with an MPCNC. The -1 and +1 keys aren’t as useful, at least to me: changing from 5 mm to 4 mm or 6 mm doesn’t make much difference.

    Jogging to align the spindle (well, a pen or drag knife) with a target using the video camera works really well:

    bCNC - Video align
    bCNC – Video align

    GRBL and bCNC don’t do smooth jogging and the discrete steps aren’t as nifty as the Joggy Thing with LinuxCNC, but it gets the job done.

  • Raspberry Pi vs. MicroSD-as-Disk Memory

    The MPCNC has bCNC running on a Raspberry Pi, with a Samsung EVO MicroSD card serving as the “hard drive”:

    Sandisk Extreme Plus vs. Samsung EVO MicroSD cards
    Sandisk Extreme Plus vs. Samsung EVO MicroSD cards

    The picture also shows a defunct Sandisk Extreme Plus killed by continuous video recording in my Fly6 bike camera. I later replaced the EVO with a video-rated Samsung card which has been running fine ever since, albeit with the occasional crash-and-reformat expected with “action” cameras.

    With that as background, a different Samsung EVO card from the same batch has been running the MPCNC’s Raspberry Pi for about a year. Over the course of a few days last week, the RPi went from an occasional stall to a complete lockup, although waiting for minutes to hours would sometimes resolve the problem. As I’ve learned by now, it’s not a software crash, it’s the controller inside the card suffering from write amplification while trying to move data from failing sectors.

    Applying f3write to the card shows the problem:

    MPCNC MicroSD - f3write slowdown
    MPCNC MicroSD – f3write slowdown

    The write speed started out absurdly high as the card’s write cache fills, then slowed to to the flash memory’s ability to absorb data, and eventually ran out of steam during the last few files.

    But, as you might not expect, f3read reported the data was fine:

    sudo f3read /mnt/part
    F3 read 7.0
    Copyright (C) 2010 Digirati Internet LTDA.
    This is free software; see the source for copying conditions.
    
                      SECTORS      ok/corrupted/changed/overwritten
    Validating file 1.h2w ... 2097152/        0/      0/      0
    Validating file 2.h2w ... 2097152/        0/      0/      0
    Validating file 3.h2w ... 2097152/        0/      0/      0
    Validating file 4.h2w ... 2097152/        0/      0/      0
    Validating file 5.h2w ... 2097152/        0/      0/      0
    Validating file 6.h2w ... 2097152/        0/      0/      0
    Validating file 7.h2w ... 2097152/        0/      0/      0
    Validating file 8.h2w ... 2097152/        0/      0/      0
    Validating file 9.h2w ... 2097152/        0/      0/      0
    Validating file 10.h2w ... 2097152/        0/      0/      0
    Validating file 11.h2w ... 2097152/        0/      0/      0
    Validating file 12.h2w ... 2097152/        0/      0/      0
    Validating file 13.h2w ... 2097152/        0/      0/      0
    Validating file 14.h2w ... 2097152/        0/      0/      0
    Validating file 15.h2w ... 2097152/        0/      0/      0
    Validating file 16.h2w ... 2097152/        0/      0/      0
    Validating file 17.h2w ... 2097152/        0/      0/      0
    Validating file 18.h2w ... 2097152/        0/      0/      0
    Validating file 19.h2w ... 2097152/        0/      0/      0
    Validating file 20.h2w ... 2097152/        0/      0/      0
    Validating file 21.h2w ... 1322894/        0/      0/      0
    
      Data OK: 20.63 GB (43265934 sectors)
    Data LOST: 0.00 Byte (0 sectors)
    	       Corrupted: 0.00 Byte (0 sectors)
    	Slightly changed: 0.00 Byte (0 sectors)
    	     Overwritten: 0.00 Byte (0 sectors)
    Average reading speed: 43.04 MB/s
    

    Obviously, the card’s read speed isn’t affected by the write problems.

    Assuming the actual data & programs on the card were still good, I slurped the partitions:

    sudo partimage save /dev/sdf1 mpcnc_boot.gz
    sudo partimage save /dev/sdf2 mpcnc_partition.gz
    

    And wrote them back:

    sudo partimage restmbr  mpcnc_boot.gz.000 
    sudo partimage restore /dev/sdf1 mpcnc_boot.gz.000 
    sudo partimage restore /dev/sdf2 mpcnc_partition.gz.000
    

    Unshown: a finger fumble requiring MBR restoration.

    Having forced the card controller to reallocate all the failed sectors, the card works now fine and runs at full speed again. This won’t last long, but it’ll be interesting to see how it plays out.

    While I was at it, I wrote the partitions to a new-ish / unused Samsung EVO Plus card, now tucked under the MPCNC’s monitor in case of emergency.

    An old SFF Optiplex with an SSD may be a better fallback.

  • American Standard Kitchen Faucet: Ceramic Valve Cores

    The  ceramic valve core from our kitchen faucet certainly qualifies for a spot on the bottom flange of the I-beam across our basement serving as a display case / collection area for shop curiosities, mementos, and the like. I am, if nothing else, a creature of fixed habits, because the spot where the core belonged already had one:

    American Standard Ceramic Faucet Valve Cores - old vs new
    American Standard Ceramic Faucet Valve Cores – old vs new

    The core on the left dates back to the 2016 replacement, so they’ve apparently decided plastic will work fine for the handle socket.

    Having the ceramic core fail after two years suggests the manufacturing process needs attention, though. I can still wring the slabs together, though, and they’d need a drop of oil to serve as bearing surfaces.

     

     

  • MPCNC: Acrylic Engraving First Light

    Just to see what happened, I chucked a drag knife in the collet pen holder:

    MPCNC Collet Pen Holder - drag knife
    MPCNC Collet Pen Holder – drag knife

    The blade, being fixed in the collet and lashed to the adapter, doesn’t rotate: it’s not behaving as a true drag knife.

    Twiddling the cut depth to about 0.2 mm produced a credible Guilloché pattern in some scrap acrylic:

    MPCNC Collet Pen Holder - drag engraved acrylic - 0.2 mm depth
    MPCNC Collet Pen Holder – drag engraved acrylic – 0.2 mm depth

    The sidelight comes from an LED flashlight off to the side, with a bit of contrast tweaking to suppress the workbench clutter.

    The MPCNC’s lack of rigidity produces visible jitters in the Guilloché pattern and backlash makes the characters somewhat wobbly, but it’s OK for a large and inexpensive CNC gantry machine.

    A brace of diamond-point engraving bits are making their way around the planet even as I type. A symmetric 60° point may reduce the swarf thrown out by the drag knife, although it surely won’t improve the overall jitter and backlash.

     

  • GCMC Guilloche Plot Generator

    It turns out the Spirograph patterns I’d been using to wring out the MPCNC are also known as Guilloché, perhaps after the guy who invented a lathe-turning machine to engrave them. Sounds pretentious, but they still look nice:

    Guilloche 591991062 - scanned
    Guilloche 591991062 – scanned

    With the ballpoint pen / knife collet holder in mind, I stripped the tool changes out of the Spirograph generator GCMC source code, set the “paper size” to a convenient 100 mm square, and tidied up the code a bit.

    As with Spirograph patterns, changing the random number seed produces entirely different results. A collection differing in the last digit, previewed online:

    Seed = 213478836:

    Guilloche 213478836
    Guilloche 213478836

    Seed = 213478837:

    Guilloche 213478837
    Guilloche 213478837

    Seed = 213478838:

    Guilloche 213478838
    Guilloche 213478838

    Seed = 213478839:

    Guilloche 213478839
    Guilloche 213478839

    They’re such unique snowflakes …

    The Bash script now accepts a single parameter to force the PRNG seed to a value you presumably want to plot again, rather than just accept whatever the Gods of Cosmic Jest will pick for you.

    The GCMC source code and Bash (or whatever) script feeding it as a GitHub Gist:

    // Spirograph simulator for MPCNC used as plotter
    // Ed Nisley KE4ZNU – 2017-12-23
    // Adapted for Guillioche plots with ball point pens – 2018-09-25
    // Spirograph equations:
    // https://en.wikipedia.org/wiki/Spirograph
    // Loosely based on GCMC cycloids.gcmc demo:
    // https://gitlab.com/gcmc/gcmc/tree/master/example/cycloids.gcmc
    // Required command line parameters:
    // -D Pen=n pen selection, 0 = no legend, 1 = current pen
    // -D PaperSize=[x,y] overall sheet size: [17in,11in]
    // -D PRNG_Seed=i non-zero random number seed
    include("tracepath.inc.gcmc");
    include("engrave.inc.gcmc");
    //—–
    // Greatest Common Divisor
    // https://en.wikipedia.org/wiki/Greatest_common_divisor#Using_Euclid's_algorithm
    // Inputs = integers without units
    function gcd(a,b) {
    if (!isnone(a) || isfloat(a) || !isnone(b) || isfloat(b)) {
    warning("GCD params must be dimensionless integers:",a,b);
    }
    local d = 0; // power-of-two counter
    while (!((a | b) & 1)) { // remove and tally common factors of two
    a >>= 1;
    b >>= 1;
    d++;
    }
    while (a != b) {
    if (!(a & 1)) {a >>= 1;} // discard non-common factor of 2
    elif (!(b & 1)) {b >>= 1;} // … likewise
    elif (a > b) {a = (a – b) >> 1;} // gcd(a,b) also divides a-b
    else {b = (b – a) >> 1;} // … likewise
    }
    local GCD = a*(1 << d); // form gcd
    // message("GCD: ",GCD);
    return GCD;
    }
    //—–
    // Max and min functions
    function max(x,y) {
    return (x > y) ? x : y;
    }
    function min(x,y) {
    return (x < y) ? x : y;
    }
    //—–
    // Pseudo-random number generator
    // Based on xorshift:
    // https://en.wikipedia.org/wiki/Xorshift
    // http://www.jstatsoft.org/v08/i14/paper
    // Requires initial state from calling script
    // -D "PRNG_Seed=whatever"
    // You can get nine reasonably random digits from $(date +%N)
    function XORshift() {
    local x = PRNG_State;
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    PRNG_State = x;
    return x;
    }
    //—–
    // Spirograph tooth counts mooched from:
    // http://nathanfriend.io/inspirograph/
    // Stators includes both inside and outside counts, because we're not fussy
    Stators = [96, 105, 144, 150];
    Rotors = [24, 30, 32, 36, 40, 45, 48, 50, 52, 56, 60, 63, 64, 72, 75, 80, 84];
    //—–
    // Start drawing things
    // Set initial parameters from command line!
    comment("PRNG seed: ",PRNG_Seed);
    PRNG_State = PRNG_Seed;
    // Define some useful constants
    AngleStep = 2.0deg;
    Margins = [12mm,12mm] * 2;
    comment("Paper size: ",PaperSize);
    PlotSize = PaperSize – Margins;
    comment("PlotSize: ",PlotSize);
    //—–
    // Set up gearing
    s = (XORshift() & 0xffff) % count(Stators);
    StatorTeeth = Stators[s];
    comment("Stator ",s,": ",StatorTeeth);
    r = (XORshift() & 0xffff) % count(Rotors);
    RotorTeeth = Rotors[r];
    comment("Rotor ",r,": ",RotorTeeth);
    GCD = gcd(StatorTeeth,RotorTeeth); // reduce teeth to ratio of least integers
    comment("GCD: ",GCD);
    StatorN = StatorTeeth / GCD;
    RotorM = RotorTeeth / GCD;
    L = to_float((XORshift() & 0x3ff) – 512) / 100.0; // normalized pen offset in rotor
    comment("Offset: ", L);
    sgn = sign((XORshift() & 0xff) – 128);
    K = sgn*to_float(RotorM) / to_float(StatorN); // normalized rotor dia
    comment("Dia ratio: ",K);
    Lobes = StatorN; // having removed all common factors
    Turns = RotorM;
    comment("Lobes: ", Lobes);
    comment("Turns: ", Turns);
    //—–
    // Crank out a list of points in normalized coordinates
    Path = {};
    Xmax = 0.0;
    Xmin = 0.0;
    Ymax = 0.0;
    Ymin = 0.0;
    for (a = 0.0deg ; a <= Turns*360deg ; a += AngleStep) {
    x = (1 – K)*cos(a) + L*K*cos(a*(1 – K)/K);
    if (x > Xmax) {Xmax = x;}
    elif (x < Xmin) {Xmin = x;}
    y = (1 – K)*sin(a) – L*K*sin(a*(1 – K)/K);
    if (y > Ymax) {Ymax = y;}
    elif (y < Ymin) {Ymin = y;}
    Path += {[x,y]};
    }
    //message("Max X: ", Xmax, " Y: ", Ymax);
    //message("Min X: ", Xmin, " Y: ", Ymin); // min will always be negative
    Xmax = max(Xmax,-Xmin); // odd lobes can cause min != max
    Ymax = max(Ymax,-Ymin); // … need really truly absolute maximum
    //—–
    // Scale points to actual plot size
    PlotScale = [PlotSize.x / (2*Xmax), PlotSize.y / (2*Ymax)];
    comment("Plot scale: ", PlotScale);
    Points = scale(Path,PlotScale); // fill page, origin at center
    //—–
    // Start drawing lines
    feedrate(1500.0mm);
    TravelZ = 1.5mm;
    PlotZ = -1.0mm;
    //—–
    // Box the outline for camera alignment
    goto([-,-,TravelZ]);
    goto([-PaperSize.x/2,-PaperSize.y/2,-]);
    goto([-,-,PlotZ]);
    foreach ( {[-1,1], [1,1], [1,-1], [-1,-1]} ; pt) {
    move([pt.x*PaperSize.x/2,pt.y*PaperSize.y/2,-]);
    }
    goto([-,-,TravelZ]);
    //—–
    // Draw the plot
    tracepath(Points, PlotZ);
    //—–
    // Add legends
    // … only for nonzero Pen
    if (Pen) {
    feedrate(250mm);
    TextFont = FONT_HSANS_1_RS;
    TextSize = [2.5mm,2.5mm];
    TextLeading = 1.5; // line spacing as multiple of nominal text height
    line1 = typeset("Seed: " + PRNG_Seed + " Stator: " + StatorTeeth + " Rotor: " + RotorTeeth,TextFont);
    line2 = typeset("Offset: " + L + " GCD: " + GCD + " Lobes: " + Lobes + " Turns: " + Turns,TextFont);
    maxlength = TextSize.x * max(line1[-1].x,line2[-1].x);
    textpath = line1 + (line2 – [-, TextLeading, -]); // undef – n -> undef to preserve coordinates
    textorg = [-maxlength/2,PaperSize.y/2 – 0*Margins.y/2 – 2*TextLeading*TextSize.y/2];
    placepath = scale(textpath,TextSize) + textorg;
    comment("Legend begins");
    engrave(placepath,TravelZ,PlotZ);
    attrpath = typeset("Ed Nisley – KE4ZNU – softsolder.com",TextFont);
    attrorg = [-(TextSize.x * attrpath[-1].x)/2,-(PaperSize.y/2 – Margins.y/2 + 2*TextLeading*TextSize.y)];
    placepath = scale(attrpath,TextSize) + attrorg;
    comment("Attribution begins");
    engrave(placepath,TravelZ,PlotZ);
    }
    goto([PaperSize.x/2,PaperSize.y/2,25.0mm]); // done, so get out of the way
    view raw Guilloche.gcmc hosted with ❤ by GitHub
    # Guilloche G-Code Generator
    # Ed Nisley KE4ZNU – September 2018
    Paper='PaperSize=[100mm,100mm]'
    Flags="-P 2"
    LibPath="-I /opt/gcmc/library"
    Spirograph='/mnt/bulkdata/Project Files/Mostly Printed CNC/Patterns/Guilloche.gcmc'
    Prolog="/home/ed/.config/gcmc/prolog.gcmc"
    Epilog="/home/ed/.config/gcmc/epilog.gcmc"
    ts=$(date +%Y%m%d-%H%M%S)
    if [ -n "$1" ] # if parameter
    then
    rnd=$1 # .. use it
    else
    rnd=$(date +%N) # .. otherwise use nanoseconds
    fi
    fn='Guilloche_'${ts}_$rnd'.ngc'
    echo Output: $fn
    p=1
    rm -f $fn
    echo "(File: "$fn")" > $fn
    #cat $Prolog >> $fn
    gcmc -D Pen=$p -D $Paper -D PRNG_Seed=$rnd $Flags $LibPath -G $Prolog -g $Epilog "$Spirograph" >> $fn
    #cat $Epilog >> $fn
    view raw guilloche.sh hosted with ❤ by GitHub
  • MPCNC: Modified Drag Knife Adapter Spring Constant

    The bars on the original MPCNC drag knife / plotter pen adapter had a 100 g/mm spring constant:

    MPCNC - Plotter pen force test
    MPCNC – Plotter pen force test

    Making the bars slightly thicker improved their print-ability:

    MPCNC knife adapter mods - OpenSCAD model
    MPCNC knife adapter mods – OpenSCAD model

    The reddish tint marks the new bars, with their location carefully tweaked to be coincident with the stock STL.

    Shoving the pen into the scale with 0.1 mm steps produces another unnervingly linear plot:

    Modified MPCNC pen adapter - Spring Constant data
    Modified MPCNC pen adapter – Spring Constant data

    Real plotter pens want about 20 g of force, so this isn’t the holder you’re looking for.

    A bunch of plots at Z=-1.0 mm turned out well with the ballpoint pen insert, though:

    MPCNC Modifed pen adapter - first plots
    MPCNC Modifed pen adapter – first plots

    The globs apparently come from plotting too fast for conditions; reducing the speed to 1500 mm/min works better.