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: Software

General-purpose computers doing something specific

  • Dremel Collet Chuck Handle

    Dremel Collet Chuck Handle

    With the set of Dremel collets organized, I made a quartet of handles for them:

    Dremel Collet Chuck Handle - group portrait
    Dremel Collet Chuck Handle – group portrait

    The idea came from the June/July 2026 Machinist’s Workshop, wherein I learned Dremel nuts / chucks fit on a 0.275 inch = 9/32 inch 40 TPI threaded body, drilled through 11/64 inch.

    Making such a thing involved some pleasant lathe time:

    Dremel Collet Chuck Handle - lathe work
    Dremel Collet Chuck Handle – lathe work

    The business end of the body has a slight taper to (ideally) match the collets:

    Dremel Collet Chuck Handle - threaded body recess
    Dremel Collet Chuck Handle – threaded body recess

    However, the collets have tapers ranging from 20° to 35°, so I defined a 60° center drill to be Good Enough™ and got a free taper while drilling the central hole.

    The collets sit in the taper:

    Dremel Collet Chuck Handle - collet installed
    Dremel Collet Chuck Handle – collet installed

    Tightening the nut closes the collet:

    Dremel Collet Chuck Handle - threaded body - nut installed
    Dremel Collet Chuck Handle – threaded body – nut installed

    The article described a nicely turned wooden handle, but a somewhat uglier 3D printed handle is fine with me:

    Dremel Collet Chuck Handle - solid model - top view
    Dremel Collet Chuck Handle – solid model – top view

    Which comes from a few lines of OpenSCAD code:

    difference() {
    
      cyl(h=HandleLength,d=HandleOD,anchor=BOTTOM,texture=Knurling,tex_size=[3.0,6.0],tex_taper=0.08) position(TOP)
        cyl(h=3.0,d=HandleOD,circum=false,rounding2=2.0,anchor=BOTTOM);
    
      down(Protrusion) {
        cyl(h=15.0,d=ShaftOD + HoleWindage,chamfer2=2.0,circum=true,anchor=BOTTOM);
        cyl(h=2*HandleLength,d=ShaftID + HoleWindage,circum=true,anchor=BOTTOM);
      }
    
    }
    

    The variables match the threaded body to my fingers:

    Protrusion = 0.1;           // make holes end cleanly
    HoleWindage = 0.2;          // make holes large enough to fit
    
    ShaftOD = 6.9;       // collet closer thread - 40 TPI 0.275 OD
    ShaftID = 4.3;       //  … internal clearance
    
    HandleOD = 15.0;
    HandleLength = 45.0;
    Knurling = "trunc_diamonds";
    

    The motivation for all this was to put the smallest taps in a holder suitable for delicate jobs. The smallest chuck on my real tap driver bottoms out on an M3 tap and can’t grip the M2 tap:

    Dremel Collet Chuck Handle - M3 vs M2 taps
    Dremel Collet Chuck Handle – M3 vs M2 taps

    I try very hard to not tap small holes, but sometimes you gotta do what you gotta do and now I’m better prepared.

    Incidentally, the first threaded body I made absolutely did not fit the Dremel nuts. After eliminating everything else, I discovered I’d set up the lathe change gears with a 20-65-45 train, rather than the 20-65-50 train required for 40 TPI with the lathe’s 16 TPI leadscrew.

    Protip: Even the best threading job (which I didn’t do on any of those things) can’t make a 36 TPI screw fit into a 40 TPI nut.

  • Manual Drive Gear for NRC Rotary Positioner

    Manual Drive Gear for NRC Rotary Positioner

    While aligning the Sherline’s laser aligner, an old rotary positioner, apparently made by NRC, emerged from the Box o’ Optical Lab equipment:

    Rotary positioner - as found
    Rotary positioner – as found

    That’s not quite “as found”, because it came festooned with the remains of an obviously lab-built Peltier-cooled laser (?) diode fixture:

    Rotary positioner - Peltier diode fixture
    Rotary positioner – Peltier diode fixture

    The positioner sported an obviously aftermarket tapped hole in the side, presumably for mounting to a support:

    Rotary positioner - tapped mounting hole
    Rotary positioner – tapped mounting hole

    The knob was apparently intended for fine angle adjustment, but it spun freely. Loosening another setscrew on the side released its well-worn parts:

    Rotary positioner - drive gear - OEM knob
    Rotary positioner – drive gear – OEM knob

    It’s not clear what the brown ring did, back when it did something, but there were no signs of stripped-off teeth or other debris in the recess; it is a very sloppy fit on the pin holding the knob. The knob may have had a compliant surface engaging the top of the ring, made with a long-since fossilized substance.

    I figured this was a great excuse to renew my acquaintance with the BOSL2 gear generator:

    Rotary positioner - drive gear - solid model
    Rotary positioner – drive gear – solid model

    It turns out the rotary ring has triangles, not gear teeth:

    Rotary positioner - tooth detail
    Rotary positioner – tooth detail

    However, setting the gear tooth pressure angle to 45° produces a reasonable triangle:

    Rotary positioner - drive gear - solid model - end view
    Rotary positioner – drive gear – solid model – end view

    Even so, getting a functional knob required many iterations, primarily because I can’t measure any of the details and had to figure the fit by cut-and-try:

    Rotary positioner - drive gear - gallery
    Rotary positioner – drive gear – gallery

    The little white dots were an excuse to use the MMU3 for multi-material printing, because why not.

    In truth, the knob doesn’t work particularly well, as the forces from the triangular teeth on the rotary ring tend to jam the knob against its pin. The knob might work better with splines driving a squishy TPU tire riding the crests of the rotary ring teeth than a real gear. Perhaps that’s what the original brown ring did before it fossilized.

    For now, the positioner returns to the Box o’ Optics Lab Stuff, because it’s the wrong hammer for the Sherline’s laser aligner. It may emerge for a future project, when I’ll have more motivation to build a functional knob.

    The OpenSCAD source code as a GitHub Gist:

    // Rotary positioner drive gear with knob
    // Ed Nisley – KE4ZNU
    include <BOSL2/std.scad>
    include <BOSL2/gears.scad>
    NumSides = 4*3*4;
    $fn=NumSides;
    Protrusion = 0.1; // make holes end cleanly
    HoleWindage = 0.2; // make holes large enough to fit
    ShaftOD = 5.5;
    Knurling = "trunc_ribs";
    difference() {
    cyl(h=9.5,d=16.0,anchor=BOTTOM,texture=Knurling,tex_size=[3.0,4.0]) position(TOP)
    cyl(h=1.8,d=15.2,circum=false,anchor=BOTTOM) position(TOP)
    color("Green")
    scale([1.03,1.03,1]) // nominal to physical hackery
    spur_gear(diam_pitch=60.0,teeth=24,pressure_angle=45,thickness=4.7,anchor=BOTTOM);
    down(Protrusion) {
    cyl(h=1.6,d=8.2 + HoleWindage,circum=true,anchor=BOTTOM);
    cyl(h=25,d=ShaftOD + HoleWindage,circum=true,anchor=BOTTOM);
    }
    }
    color("Red")
    right((8.2 + 16.0)/4)
    cyl(h=0.6,d=2.0,circum=false,anchor=BOTTOM);

  • Software Testing, Lack Thereof

    Software Testing, Lack Thereof

    Not what you want to see when trying to extract a month’s worth of data from your blood pressure meter:

    Omron - Bluetooth sync error message
    Omron – Bluetooth sync error message

    Perhaps the format string should be $1$s or %1%s, not a mixture?

    It’s typical for Omron to issue two updates in quick succession after months of no activity, so programming by successive approximation may be a thing.

  • Sherline vs. LinuxCNC 2.9.8

    Sherline vs. LinuxCNC 2.9.8

    Notes on finally getting the Sherline CNC mill operating in its new home, with a suitable Axis startup image:

    Sherline setup 2026-06
    Sherline setup 2026-06

    The gray countertop from its former home sits on foam strips soaking up a slight warp with enough isolation to keep things quiet.

    The gantry required the usual fiddling to make the cable hoist the Z axis directly upward, with the orange flag on the counterweight barely visible below the monitor.

    I recently touched the box of precision XYZ positioners and there might be something useful, albeit grossly overqualified, in there to simplify dropping the laser pointer beam directly through the spindle bore.

    A clean installation of LinuxCNC 2.9.8 on an ancient Dell Optiplex 9020 proceeded smoothly. Installing x11vnc let the rest of the proceedings happen from upstairs in the Comfy Chair. For unknown reasons, vinagre works better than Reminna as a VNC client, after recalling F11 enters / exits fullscreen mode.

    The mesaflash utility accompanying LinuxCNC 2.9.8 did not recognize the Mesa 6i25 card. Fetching & compiling the most recent version (3.5.17) cleared that hump and flashed the bitmap:

    sudo mesaflash --verbose --device 5i25 --write 5i25/configs/hostmot2/5i25_prob_rfx2.bit
    

    The 6i25 wants to be known as a 5i25, with its jumpers in their default positions:

    Mesa 6i25 - jumper locations
    Mesa 6i25 – jumper locations

    For unknown reasons, the button originally known as btn-trigger became btn-joystick for a while and has now reverted to btn-trigger. It’s labeled 1 in the four-button cluster:

    Logitech Dual-Action Gamepad - relabeled
    Logitech Dual-Action Gamepad – relabeled

    Which required changing the pin name in the Kicad library component:

    Sherline HAL schematic - Logitech button 1 name
    Sherline HAL schematic – Logitech button 1 name

    Which required converting the old Kicad library format into the new Kicad library format, a completely automatic process without, AFAICT, any unpleasant side effects.

    The new name fed into the schematic as expected, after minor fumbling while re-adding the modified component and setting its annotation number:

    Sherline HAL schematic - Logitech AZ button logic
    Sherline HAL schematic – Logitech AZ button logic

    The X axis home microswitch has (apparently) become more bouncy while it was idle, so I increased the number of samples before HAL sees a change in that GPIO input:

    Sherline HAL schematic - home switch debounce
    Sherline HAL schematic – home switch debounce

    The dbounce block runs in the servo thread at 1 m per tick, so those 20 samples take all of 20 ms while the X axis moves 0.15 mm. At some point I should apply a scope to that switch, but for now It Just Works™.

    Considerably to my surprise, compiling the modified Kicad schematic into a HAL file proceeded without incident, despite various Python updates in the last five years.

    Kicad produces an “intermediate XML file” containing the netlist data intended for a conversion / export program, which is basically what my Kicad-to-HAL lashup does. I told Kicad to use Bash’s true command as a converter:

    Sherline HAL schematic - netlist export
    Sherline HAL schematic – netlist export

    So I can run the Kicad-to-HAL converter manually:

    python ../Kicad\ Conversion/Kicad-to-HAL.py Sherline\ HAL\ -\ Logitech\ Gamepad\ jogging.xml Sherline.hal
    

    You could tell Kicad to run it and it would probably Just Work™, but I’m used to peering at the results and dinking with my program.

    Which produced a new Sherline.hal file that Just Worked™ with the existing Sherline.ini file containing the configuration constants.

    The Sherline has cut only air so far, but, as the man said, “E pur si muove.”

  • Photo Backdrop: Crossbar Improvement

    Photo Backdrop: Crossbar Improvement

    The instructions for the Photo Backdrop frame (upon which we hang Mary’s quilts for photos) suggest the crossbar fits on like this:

    Photo Backdrop - OEM crossbar installation
    Photo Backdrop – OEM crossbar installation

    The slot in the bottom is wider than the M10 stud, so the crossbar tends to flop around while assembling it overhead. I immediately replaced the wingnut with a chunky knob for better griptitude, but was never happy with how poorly the whole thing fit together.

    This is dramatically better:

    Photo Backdrop - fitting installed
    Photo Backdrop – fitting installed

    The crossbar is upside-down from the OEM instructions, but the bottom of the gray plug holds the tube firmly to the tripod while the nut seats firmly on the plug’s flat top:

    Photo Backdrop - fitting top view
    Photo Backdrop – fitting top view

    A snippet of 3M 300LSE adhesive sheet holds the plug in place, so that’s one less thing to fiddle with on each end.

    The solid model holds no surprises:

    Photo Backdrop Fittings - solid model
    Photo Backdrop Fittings – solid model

    Of course, it builds with the flat end downward.

    The OpenSCAD source code as a GitHub Gist:

    // Photo backdrop fitting
    // Ed Nisley – KE4ZNU
    // 2026-07-01
    include <BOSL2/std.scad>
    Layout = "Show"; // [Build,Show]
    /* [Hidden] */
    ID = 0;
    OD = 1;
    LENGTH = 2;
    HoleWindage = 0.2;
    Protrusion = 0.01;
    NumSides = 3*2*4;
    Clearance = 0.3;
    $fn=NumSides;
    Tube = [23.0 – HoleWindage,1*INCH,100.0]; // arbitrary length
    Aperture = [24.0,15.0,100.0]; // oblong nut hole, arbitrary Z
    Washer = [10.5,20.0,1.5]; // M10
    //—–
    // Define things
    module Fitting() {
    difference() {
    union() {
    intersection() {
    xcyl(Tube[LENGTH],d=Tube[ID]);
    cuboid(Aperture,rounding=Aperture.y/2,edges="Z");
    }
    cuboid([Aperture.x,Aperture.y,Tube[OD]/2],rounding=Aperture.y/2,edges="Z",anchor=BOTTOM);
    }
    cyl(Aperture.z,d=Washer[ID]);
    }
    }
    //—–
    // Build things
    if (Layout == "Show") {
    Fitting();
    }
    if (Layout == "Build") {
    up(Tube[OD]/2)
    xrot(180)
    Fitting();
    }

  • Keychron C3 Pro Keyboard: Taming

    Keychron C3 Pro Keyboard: Taming

    Having set the Moonlander to use Auto Shift, I’ve come to depend on it, so I got a Keychron C3 Pro keyboard for one of the Basement Shop’s PCs because it glows in the dark and can be configured with QMK:

    Keychron C3 Pro - tamed
    Keychron C3 Pro – tamed

    The default setup has rainbow hues cycling across the keyboard, which I find entirely too distracting. Although you can manually select the solid-color variant from the myriad possibilities using the keyboard, I forced a solid color with this config.h file:

    #define RGB_MATRIX_DEFAULT_ON true // Sets the default enabled state, if none has been set
    #define RGB_MATRIX_DEFAULT_MODE RGB_MATRIX_SOLID_COLOR // Sets the default mode, if none has been set
    #define RGB_MATRIX_DEFAULT_HUE 36  // Sets the default hue value, if none has been set
    #define RGB_MATRIX_DEFAULT_SAT 255 // Sets the default saturation value, if none has been set
    #define RGB_MATRIX_DEFAULT_VAL 255 // Sets the default brightness value, if none has been set
    
    

    Enabling Auto Shift requires this rules.mk file:

    AUTO_SHIFT_ENABLE = yes
    

    Both of those go in the keymap directory defining the keyboard mapping for my custom setup:

    qmk_firmware/keyboards/keychron/c3_pro/ansi/rgb/keymaps/ednisley
    

    The keymap.c file remains unchanged, although I’m mildly tempted to toss the Mac layouts overboard.

    For the record, setting QMK to compile that keyboard configuration goes like this:

    qmk setup -H /base_directory/…/qmk_firmware
    <snippage>
    qmk new-keymap -kb keychron/c3_pro/ansi/rgb -km ednisley
    qmk config user.keyboard=keychron/c3_pro/ansi/rgb user.keymap=ednisley
    qmk compile
    qmk flash
    

    Flashing the keyboard firmware goes like this:

    • Run qmk flash
    • Unplug the USB cable
    • Hold down the Esc key
    • Plug in the USB cable
    • Release the Esc key

    I should boot the Atreus configuration into the current decade, but that’s for later.

  • Outlet Strip Bench Mount

    Outlet Strip Bench Mount

    A spate of tidying-up led to mounting an outlet strip along the back of a bench:

    Outlet Bench Mount - installed
    Outlet Bench Mount – installed

    Rather than drill holes into the top of the bench for those screws, they fit into M4 brass inserts heat-staked into the brackets:

    Outlet Bench Mount - show view
    Outlet Bench Mount – show view

    The holes for those inserts aren’t centered side-to-side on the brackets, because the screw holes aren’t centered on the bent-steel angles forming the outlet strip endplates.

    The bottom arm on the brackets probably isn’t necessary, but they kept the outlet strip from crawling away while I match-drilled two holes for the screws into the side of the benchtop.

    For obvious reasons, the brackets print on their sides:

    Outlet Bench Mount - build view
    Outlet Bench Mount – build view

    Another outlet strip from a different manufacturer is, of course, different, but changing three parameters in the OpenSCAD program summons a different bracket from the vasty digital deep:

    Outlet Bench Mount - different brand
    Outlet Bench Mount – different brand

    Parametric modeling and a 3D printer are exactly the right hammers for the job …

    The OpenSCAD source code as a GitHub Gist:

    // Shower soap dish
    // Ed Nisley – KE4ZNU
    // 2026-06-04
    include <BOSL2/std.scad>
    Layout = "Show"; // [Show,Build]
    /* [Hidden] */
    HoleWindage = 0.2;
    Protrusion = 0.1;
    NumSides = 3*3*4;
    Gap = 10.0/2;
    $fn=NumSides;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    BenchThick = 21.0; // workbench top
    ScrewOD = 4.0; // into edge of bench
    Insert = [4.0,5.5,10.0]; // robust M4 insert
    WallThick = 10.0;
    BaseThick = 10.0;
    OutletBase = [15.0,40.0];
    HoleOffset = 6.5; // from outside edge of bracket
    HoleOC = 24.0;
    MountOA = [OutletBase.x,OutletBase.y,BenchThick + Insert[LENGTH] + 1.0 + BaseThick];
    //———-
    // Build it
    module Mount() {
    difference() {
    cuboid(MountOA,rounding=1.0,anchor=BOTTOM + BACK);
    up(BaseThick)
    fwd(WallThick)
    cuboid([2*MountOA.x,MountOA.y,BenchThick],anchor=BOTTOM + BACK);
    up(BaseThick + BenchThick/2) back(Protrusion)
    ycyl(OutletBase.y,d=ScrewOD,circum=true,$fn=6,anchor=BACK);
    for (j=[-1,1])
    fwd(MountOA.y/2 + j*HoleOC/2)
    right(HoleOffset – MountOA.x/2)
    up(MountOA.z + Protrusion)
    cyl(Insert[LENGTH],d=Insert[OD],circum=true,$fn=6,anchor=TOP);
    }
    }
    //———-
    // Build it
    if (Layout == "Show") {
    left(Gap + MountOA.x/2)
    Mount();
    right(Gap + MountOA.x/2)
    xflip() Mount(); // mirror for the other end of the outlet strip
    }
    if (Layout == "Build") {
    left(MountOA.z/2)
    up(MountOA.x/2)
    yrot(90)
    Mount();
    fwd(1.5*MountOA.y)
    left(MountOA.z/2 – BenchThick/2 – Insert[LENGTH]/2)
    zrot(180)
    up(MountOA.x/2)
    yrot(-90)
    xflip() Mount(); // mirror for the other end of the outlet strip
    }