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.

Author: Ed

  • GCMC XY Axis Calibration Target

    GCMC XY Axis Calibration Target

    The CNC-3018XL drawing the scales on a Tek Circuit Computer disagreed with the MPCNC cutting the perimeter. The Y axis edges looked OK:

    Tek CC - 2021-11 - Y detail
    Tek CC – 2021-11 – Y detail

    But the cut on the X axis edges went too close to the tips:

    Tek CC - 2021-11 - X detail
    Tek CC – 2021-11 – X detail

    I conjured a calibration target to help measure the two machines:

    Cal Target - CNC3018XL
    Cal Target – CNC3018XL

    The X- side of the plot gives the general idea:

    CNC-3018XL - Backlash Test - 400step-mm
    CNC-3018XL – Backlash Test – 400step-mm

    The vertical lines consist of two halves, drawn in order from left to right on the top and right to left on the bottom, meeting in the middle at the Y=0 axis. If they do, in fact, meet in the middle, then there’s no problem with backlash.

    The 25 mm distance between adjacent lines verifies the linear calibration; the total distance along the X and Y axes provides more travel for more error accumulation.

    The circles provide some reassurance the machine can draw a smooth circle, because they come from GRBL’s (or whatever) G2 G-Code commands, not a linear approximation.

    Spoiler: after a considerable amount of drawing, measuring, and muttering, the problem emerged from the CNC-3018XL’s X-axis leadscrew:

    Cal Target - 400 step-mm - merged
    Cal Target – 400 step-mm – merged

    It’s half a millimeter short on each end!

    More on this tomorrow …

    The GCMC source code as a GitHub Gist:

    (epilog begins)
    (bCNC may regard plot as done before this returns)
    M2
    (epilog ends)
    view raw epilog.gcmc hosted with ❤ by GitHub
    (prolog begins)
    G17 (XY plane)
    G21 (mm)
    G40 (no cutter comp)
    G49 (no tool length comp)
    G80 (no motion mode)
    G90 (abs distance)
    G94 (units per minute)
    (prolog ends)
    view raw prolog.gcmc hosted with ❤ by GitHub
    // Grid pattern to check XY scaling
    // Ed Nisley KE4ZNU – 2021-11
    // gcmc -P 4 –pedantic –prolog prolog.gcmc –epilog epilog.gcmc –output 'Scale Grid.ngc' 'Scale Grid.gcmc'
    include("engrave.inc.gcmc");
    FALSE = 0;
    TRUE = !FALSE;
    //—–
    // Define useful constants
    SafeZ = [-,-,10.0mm]; // above all obstructions
    TravelZ = [-,-,2.0mm]; // within engraving / milling area
    PenZ = [-,-,-1.0mm]; // depth for good inking
    PenSpeed = 2000mm;
    //—–
    // Overall values
    PlotSize = [250mm,200mm,-];
    comment("PlotSize: ",PlotSize);
    GridSize = [25mm,25mm,-];
    Margins = [5mm,5mm,-];
    CenterOD = 5.0mm;
    TextFont = FONT_HSANS_1_RS; // single stroke stick font
    TextSize = 3.0 * [1.0mm,1.0mm];
    //—–
    // Draw it
    feedrate(PenSpeed);
    comment("Draw title info");
    tp = scale(typeset("Scale & Backlash Test Pattern",TextFont),TextSize);
    tp += [-PlotSize.x/2 + GridSize.x/2,PlotSize.y/2 – GridSize.y/2,-];
    engrave(tp,TravelZ.z,PenZ.z);
    tp = scale(typeset("Grid " + GridSize,TextFont),TextSize);
    tp += [-PlotSize.x/2 + GridSize.x/2,PlotSize.y/2 – GridSize.y/2 – 1.5*TextSize.y,-];
    engrave(tp,TravelZ.z,PenZ.z);
    tp = scale(typeset("F " + PenSpeed + "/min",TextFont),TextSize);
    tp += [-PlotSize.x/2 + GridSize.x/2,PlotSize.y/2 – GridSize.y/2 – 3.0*TextSize.y,-];
    engrave(tp,TravelZ.z,PenZ.z);
    tp = scale(typeset("Ed Nisley – KE4ZNU",TextFont),TextSize);
    tp += [-PlotSize.x/2 + GridSize.x/2,-(PlotSize.y/2 – GridSize.y/2),-];
    engrave(tp,TravelZ.z,PenZ.z);
    tp = scale(typeset("softsolder.com",TextFont),TextSize);
    tp += [-PlotSize.x/2 + GridSize.x/2,-(PlotSize.y/2 – GridSize.y/2 + 1.5*TextSize.y),-];
    engrave(tp,TravelZ.z,PenZ.z);
    comment("Mark center point");
    goto(SafeZ);
    goto([CenterOD/2,0,-]);
    move(PenZ);
    circle_cw([0,0]);
    comment("Label axes");
    tp = scale(typeset("X+",TextFont),TextSize);
    tp += [GridSize.x + 0.5*TextSize.x,-TextSize.y/2,-];
    engrave(tp,TravelZ.z,PenZ.z);
    tp = scale(typeset("Y+",TextFont),TextSize);
    tp += [-TextSize.x/2,GridSize.y + 0.5*TextSize.y,-];
    engrave(tp,TravelZ.z,PenZ.z);
    comment("Draw left-to-right");
    tp = scale(typeset("L to R →",TextFont),TextSize);
    tp += [-PlotSize.x/2 + GridSize.x/2 – tp[-1].x/2,GridSize.y/2,-];
    engrave(tp,TravelZ.z,PenZ.z);
    goto([-(PlotSize.x/2 + Margins.x),GridSize.y,-]);
    for (p=[-PlotSize.x/2,GridSize.y,-] ; p.x <= PlotSize.x/2 ; p.x += GridSize.x ) {
    comment(" p: ",p);
    goto(p);
    move(PenZ);
    move_r([-,-GridSize.y,-]);
    goto(TravelZ);
    }
    comment("Draw right-to-left");
    tp = scale(typeset("R to L ←",TextFont),TextSize);
    tp += [PlotSize.x/2 – GridSize.x/2 – tp[-1].x/2,-GridSize.y/2,-];
    engrave(tp,TravelZ.z,PenZ.z);
    goto([(PlotSize.x/2 + Margins.x),-GridSize.y,-]);
    for (p=[PlotSize.x/2,-GridSize.y,-] ; p.x >= -PlotSize.x/2 ; p.x -= GridSize.x ) {
    comment(" p: ",p);
    goto(p);
    move(PenZ);
    move_r([-,GridSize.y,-]);
    goto(TravelZ);
    }
    comment("Draw bottom-to-top");
    tp = scale(typeset("B to T ↑",TextFont),TextSize);
    tp += [-GridSize.x/2 – tp[-1].x/2,-(PlotSize.y/2 – TextSize.y),-];
    engrave(tp,TravelZ.z,PenZ.z);
    goto([-GridSize.x,-(PlotSize.y/2 + Margins.y),-]);
    for (p=[-GridSize.x,-PlotSize.y/2,-] ; p.y <= PlotSize.y/2 ; p.y += GridSize.y ) {
    comment(" p: ",p);
    goto(p);
    move(PenZ);
    move_r([GridSize.x,-,-]);
    goto(TravelZ);
    }
    comment("Draw top-to-bottom");
    tp = scale(typeset("T to B ↓",TextFont),TextSize);
    tp += [GridSize.x/2 – tp[-1].x/2,(PlotSize.y/2 – 1.5*TextSize.y),-];
    engrave(tp,TravelZ.z,PenZ.z);
    goto([GridSize.x,(PlotSize.y/2 + Margins.y),-]);
    for (p=[GridSize.x,PlotSize.y/2,-] ; p.y >= -PlotSize.y/2 ; p.y -= GridSize.y ) {
    comment(" p: ",p);
    goto(p);
    move(PenZ);
    move_r([-GridSize.x,-,-]);
    goto(TravelZ);
    }
    comment("Draw circles");
    maxr = (PlotSize.x < PlotSize.y) ? PlotSize.x/2 : PlotSize.y/2;
    for (r=GridSize.x/2 ; r <= maxr ; r += GridSize.x) {
    comment(" r: ",r);
    goto([-r,0,-]);
    move(PenZ);
    circle_cw([0,0,-]);
    goto(TravelZ);
    }
    goto(SafeZ);
    goto([0,0,-]);
    view raw Scale Grid.gcmc hosted with ❤ by GitHub

  • ShopVac Nozzle Caddy

    ShopVac Nozzle Caddy

    Shortly after acquiring the Greatest ShopVac, I zip-tied half a foot of cardboard tube to the handle to corral the nozzle and keep the ungainly hose from sprawling across the floor. While disembowling the Ottlite into a mini-lathe light, the plastic trim joining the baseplate to the vertical tube cried out to become a nozzle caddy:

    ShopVac Nozzle Caddy - front view
    ShopVac Nozzle Caddy – front view

    It was exactly the right size and shape (by my admittedly slack standards) to hold the nozzle, plus being destined for the trash, so all it needed was a pair of clamp brackets conjured from the vasty digital deep:

    ShopVac Nozzle Caddy - solid model
    ShopVac Nozzle Caddy – solid model

    The bosses fit into a tapered slot along what was the rear side, with a pair of 4 mm holes at each end for screws into threaded brass inserts epoxied into the brackets:

    ShopVac Nozzle Caddy - clamps mounted
    ShopVac Nozzle Caddy – clamps mounted

    They obviously descend from the many clamp mounts I’ve made for everything from garden hoses to bike running lights. A pair of 4 mm SHCS squish the clamp around the handle, with a strip of electrical tape improving plastic-to-metal griptivity:

    ShopVac Nozzle Caddy - side view
    ShopVac Nozzle Caddy – side view

    The clearance just barely allows a nylock nut atop a washer and you’ll want to trim those 40 mm screws to an exact fit, but it came out pretty well.

    The original dimension doodle with some modeling ideas that didn’t survive more thinking:

    ShopVac Nozzle Caddy - Dimension Doodle 1
    ShopVac Nozzle Caddy – Dimension Doodle 1

    A more detailed doodle with brass inserts instead of the nylock nuts and an aluminum spreader plate that was obviously not necessary:

    ShopVac Nozzle Caddy - Dimension Doodle 2
    ShopVac Nozzle Caddy – Dimension Doodle 2

    In retrospect, the inserts would make more sense.

    The angle doodles convinced me not to bother modeling either the slot’s taper along its length or its mold draft.

    Kinda looks like it grew there and makes one wonder why they don’t include a caddy as a standard option.

    The OpenSCAD source code as a GitHub Gist:

    // ShopVac Nozzle Caddy
    // Ed Nisley KE4ZNU 2022-02
    Layout = "Show"; // [Handle,Block,Show,Build]
    HandleOD = 20.0;
    //- Extrusion parameters must match reality!
    /* [Hidden] */
    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);
    ID = 0;
    OD = 1;
    LENGTH = 2;
    //———-
    // Dimensions
    // Handle lies along X axis
    Handle = [200,HandleOD,HandleOD]; // X = longer than anything else
    WallThick = 5.0; // Thinnest printed wall
    Screw = [4.0,7.0,25.0]; // M4 socket head cap screw
    Washer = [4.5,9.0,0.8]; // M4 washer
    Insert = [4.0,5.9,10.0]; // M4 brass insert
    Block = [15.0,Handle.y + 4*WallThick + 2*Screw[ID],HandleOD + 2*WallThick]; // overall clamp block
    echo(str("Block: ",Block));
    Bosses = [[Block.x,9.5,13.0],[Block.x,15.0,9.0]];
    ScrewOC = Handle.y + 2*WallThick + Screw[ID];
    Kerf = 1.0; // cut through middle to apply compression
    Gap = 1.25;
    CornerRadius = Washer[OD]/2;
    //———————-
    // 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);
    }
    // Shopvac handle
    module Handle() {
    rotate([0,90,0])
    translate([0,0,-Handle.x/2])
    rotate(180/(4*8))
    PolyCyl(Handle.y,Handle.x,4*8);
    }
    // Clamp block
    module ClampBlock(BossID=0) {
    difference() {
    union() {
    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=8);
    translate([0,0,-(Block.z/2 + Bosses[BossID].z/2 – Protrusion)])
    cube(Bosses[BossID],center=true);
    }
    for (j = [-1,1]) // screw holes
    translate([0,j*ScrewOC/2,-(Block.z/2 + Protrusion)])
    rotate(180/6)
    PolyCyl(Screw[ID],Block.z + 2*Protrusion,6);
    cube([2*Block.x,2*Block.y,Kerf],center=true);
    Handle();
    translate([0,0,-Block.z])
    rotate(180/6)
    PolyCyl(Screw[ID],Block.z,6);
    translate([0,0,-(Handle.z/2 + Insert[LENGTH])])
    rotate(180/6)
    PolyCyl(Insert[OD],Handle.y,6);
    }
    }
    // Splice block less handle bore
    module ShapedBlock() {
    difference() {
    ClampBlock();
    Handle();
    }
    }
    //———-
    // Build them
    if (Layout == "Handle")
    Handle();
    if (Layout == "Block")
    ClampBlock(BossID=0);
    if (Layout == "Show") {
    color("Green",0.25)
    Handle();
    xofs = -((len(Bosses) – 1)/2 * Gap*Block.x);
    for (i=[0:len(Bosses) – 1])
    translate([xofs + i*Gap*Block.x,0,0])
    ClampBlock(i);
    }
    if (Layout == "Build") {
    yofs = -((len(Bosses) – 1)/2 * Gap*Block.y);
    for (j=[0:len(Bosses) – 1])
    translate([0,yofs + j*Gap*Block.y,0])
    translate([0,0,Block.x/2])
    rotate([0,90,0])
    ClampBlock(j);
    }

  • Fluorescent Shop Light Ballast Caps

    Fluorescent Shop Light Ballast Caps

    It never ceases to amaze me that these capacitors appear in the AC power line circuits inside old-school fluorescent shop lights:

    Shop light ballast cap
    Shop light ballast cap

    It really is a capacitor:

    Shop light ballast cap A - test
    Shop light ballast cap A – test

    Its sibling from the other end of the fixture had more ESR:

    Shop light ballast cap B - test
    Shop light ballast cap B – test

    Both were likely within spec, whatever that means.

    I have no idea what’s lurking inside the tidy LED tubes now living in that same fixture, of course.

  • Sheath Your Blades!

    Sheath Your Blades!

    Trigger warning: gore.

    A week ago I milled a stack of cursor blanks, then engraved a test hairline on a scrap cursor to make sure everything was ready:

    Cursor V-bit setup
    Cursor V-bit setup

    After raising the spindle a few inches, I reached across the table, peeled the tape, and, as I pulled my hand back with the finished cursor, snagged the back of my left index finger on the V bit.

    So. Much. Blood.

    Urgent Care PA: “You may have nicked the tendon. Get thee hence to the Hospital Trauma Center.”

    Trauma Center MD: “See that white fiber down in there? That’s the extensor ligament. Looks OK and should heal fine.”

    Me: “Urp.”

    Trauma Center MD: “Unless you’re one of the 20% who get an infection.”

    Me: “Unless I’m one of the few who contract an MRSA infection, then just up and die.”

    Trauma Center MD: “Well, yes, there’s that. If the wound swells or smells bad, come back here quickly.”

    Dutchess County is now on the trailing edge of the Omicron wave, but the Trauma Center is attached to the Emergency Room and had a steady stream of customers arriving by ambulance. While being entirely content to not be their most urgent case, I had plenty of time to examine the wide variety of instruments parked in the room with me:

    Nameless Hospital Cart
    Nameless Hospital Cart

    I’m on a ten-day regimen of surprisingly inexpensive Amoxicillin + Clavulanate Potassium capsules, which is apparently what it takes to knock down a potential infection these days.

    Five days later, it looks like I should pull through:

    Lacerated Left Index Finger
    Lacerated Left Index Finger

    So I hereby swear a mighty oath on the bones of my ancestors to always sheath my blades. You should, too.

    But we all knew that last week, didn’t we?

  • Underwriter’s Knot

    Underwriter’s Knot

    Found inside a fluorescent desk lamp being salvaged for possible use as an LED task lamp:

    Fluorescent Desk Lamp - Underwriters Knot
    Fluorescent Desk Lamp – Underwriters Knot

    It’s one of the few Underwriter’s Knots I’ve ever seen in the wild. Many recent (i.e., built in the last half-century) lamps pass the cords through a plastic clamp or depend on simple bushings, with some just ignoring the problem.

    This anonymous lamp sports the usual Made in China sticker, but also features a genuine-looking UL sticker complete with elaborate holograms, so it may well have been sold by a reputable company. IIRC, it came from a trash can in a Vassar College hallway, back when in-person meetings were a thing; perhaps Vassar required known-good electrical hardware.

  • CNC-3018XL: Improved X-Axis Home Switch Mount

    CNC-3018XL: Improved X-Axis Home Switch Mount

    A few months of inactivity left the CNC-3018XL table parked in its homed position where the gentle-but-inexorable pressure of the switch lever displaced the foam holding the plastic actuator tab on the X-axis bearing enough that it would no longer operate reliably:

    3018 CNC - Y axis endstop
    3018 CNC – Y axis endstop

    Putting foam tape in a highly leveraged position produces the same poor results as in finance.

    The fix requires reorienting the switch so a solid block on the bearing can push directly on the actuator lever:

    CNC-3018 X Home Switch - bottom view
    CNC-3018 X Home Switch – bottom view

    The block must curve around the bearing to give the tape enough surface area for a good grip:

    CNC-3018 X Home Switch - oblique view
    CNC-3018 X Home Switch – oblique view

    The solid model for the new X-axis mount looks about like you’d expect:

    CNC-3018 X Home Switch Mount - solid model
    CNC-3018 X Home Switch Mount – solid model

    I increased the home switch pulloff to 2 mm, although it’s not clear that will make any difference in the current orientation.

    The OpenSCAD source code as a GitHub Gist:

    // 3018-Pro Mount for Makerbot Endstop PCB
    // Ed Nisley KE4ZNU – 2019-07 (using OEM machine axes)
    // 2022-02-02 rotate X block (after renaming axes to match new layout)
    /* [Build Options] */
    Layout = "Show"; // [Build, Show]
    /* [Hidden] */
    ThreadThick = 0.25; // [0.20, 0.25]
    ThreadWidth = 0.40; // [0.40]
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    Protrusion = 0.01; // [0.01, 0.1]
    HoleWindage = 0.2;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    //- Shapes
    // Basic PCB with hole for switch pins
    // origin at switch actuator corner, as seen looking at component side
    SwitchClear = [15.0,5.0,2.0]; // clearance around switch pins
    SwitchOffset = [12.5,9.0,0.0]; // center of switch pins from actuator corner
    PCB = [26.0,16.4,2*SwitchClear.z]; // switch PCB beyond connector, pin height
    //XBlock = [PCB.x + 10.0,PCB.y,20.0];
    XBlock = [PCB.x,PCB.y,10.0];
    XBearing = [10.0,26.5,28.5];
    XPin = [10.0,20.0,10.0];
    module XMount() {
    if (false) // side-push switch tended to slip
    difference() {
    translate([-10.0,0,0])
    cube(XBlock,center=false);
    translate([0,-Protrusion,10.0])
    cube(XBlock + [0,2*Protrusion,0],center=false);
    translate(SwitchOffset + [0,0,10.0 – SwitchClear.z/2])
    cube(SwitchClear + [0,0,Protrusion],center=true);
    }
    else {
    difference() {
    cube(XBlock,center=false);
    translate(SwitchOffset + [0,0,XBlock.z – SwitchClear.z/2])
    cube(SwitchClear + [0,0,Protrusion],center=true);
    }
    translate([1.25*XBlock.x,0,0])
    difference() {
    cube(XPin + [0,0,XBearing[OD]/4],center=false);
    translate([-Protrusion,XPin.y/2,XPin.z + XBearing[OD]/2])
    rotate([0,90,0])
    cylinder(d=XBearing[OD],h=XPin.x + 2*Protrusion,center=false);
    translate([-Protrusion,-XPin.y/2,XPin.z])
    cube(XPin + [2*Protrusion,0,0],center=false);
    }
    }
    }
    YBlock = [PCB.x,PCB.y,5.0];
    module YMount() {
    difference() {
    cube(YBlock,center=false);
    translate(SwitchOffset + [0,0,YBlock.z – SwitchClear.z/2])
    cube(SwitchClear + [0,0,Protrusion],center=true);
    }
    }
    ZBlock = [PCB.x,PCB.y,6.0];
    ZPin = [20.0,10.0,5.5];
    module ZMount() {
    difference() {
    cube(ZBlock,center=false);
    translate(SwitchOffset + [0,0,ZBlock.z – SwitchClear.z/2])
    cube(SwitchClear + [0,0,Protrusion],center=true);
    }
    translate([1.25*ZBlock.x,0,0])
    difference() {
    cube(ZPin,center=false);
    translate([ZPin.x/2,-Protrusion,4.0])
    cube(ZPin + [0,2*Protrusion,0],center=false);
    }
    }
    //- Build things
    if (Layout == "Show") {
    translate([0,XBlock.y,0])
    YMount();
    translate([0,-XBlock.y/2])
    XMount();
    translate([0,-(ZBlock.y + XBlock.y)])
    ZMount();
    }

  • Inkjet Refilling: End of an Era

    Inkjet Refilling: End of an Era

    Just before the turn of the millennium, I bought what turned out to be a never-sufficiently-to-be-damned HP 2000C inkjet printer that served as my introduction to refilling inkjet cartridges. A few years later, a Canon S630 printer joined the stable and worked fine for perhaps five years before succumbing to a printhead death. An Epson R380 that might have cost fifteen bucks after rebate took over, drank maybe a gallon of knockoff ink through a continuous ink supply system during the next thirteen years, and finally suffered progressive printhead failure during the last year.

    Something recently changed in the inkjet market: Epson (among others) now touts their “Ecotank” printers featuring large internal reservoirs refilled by 70 ml bottles of color ink priced at perhaps 20¢/ml, obtained direct from Epson via Amazon. They proudly note you can save 90% off the cost of cartridges (“Kiss Expensive Cartridges Goodbye”), without mentioning how their previous extortionate cartridge business made that possible. Of course, Ecotank printers cost far more than cartridge-based printers, but that seems reasonable to me.

    Because the ink bottles fit neatly into the printer through a push-to-flow valve interlock, I can finally retire this relic:

    Inkjet refilling towel
    Inkjet refilling towel

    That’s maybe fifteen years of accumulated splotches.

    I hope my refusal to buy their cartridges helped immanentize their eschaton, just a little.

    Good riddance.