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

  • PolyDryer Humidity: November

    PolyDryer Humidity: November

    The measurements:

    2025-11-042025-11-112025-11-19
    Filament%RH%RHWeight – gWt gain – gGain %%RH
    PETG White393927.42.49.6%23
    PETG Black252626.71.76.8%14
    PETG Orange302326.51.56.0%23
    PETG Blue182327.02.08.0%10
    PETG-CF Blue252526.51.56.0%18
    PETG-CF Black222226.31.35.2%14
    PETG-CF Gray282826.51.56.0%18
    Empty → PETG Clear31n/a26.81.87.2%18
    TPU – Clear282926.31.35.2%14
    W empty → TPU – K232726.81.87.2%18

    All the boxes now have filament spools and 50 g of silica gel divided equally between the humidity meter and the tray in the bottom of the box:

    Polydryer Box desiccant tray - installed
    Polydryer Box desiccant tray – installed

    The PETG White in the first row is the new spool loaded last month. I think the 39 %RH indicates the spools do not necessarily arrive bone-dry in their vacuum-sealed bags with a tiny desiccant packet.

    Conversely, both the PETG Clear and TPU K filaments are new spools that seem reasonably dry out of their bags.

    The auto-rewind spindle in the PETG Orange filament hasn’t been working quite right, so I opened the box a few times. It now has a new PETG-CF spindle.

  • Bird Feeder Tray Mount

    Bird Feeder Tray Mount

    The mixed flock attending the bird feeder in the back yard scatters enough seeds to attract the deer, so I added a tray underneath to catch the overspray:

    Bird Feeder Tray Mount - installed
    Bird Feeder Tray Mount – installed

    Well, two trays, because it took a couple of iterations to make the solid model match reality:

    Bird Feeder Tray Mount - show layout
    Bird Feeder Tray Mount – show layout

    The n-1 iteration was Close Enough™ and two trays are obviously better than one.

    The “trays” are stray lids from the six gallon buckets we use for many purposes, including root-cellaring the vegetable garden harvest. The lid’s solid model was straightforward:

    Bird Feeder Tray Mount - lid model
    Bird Feeder Tray Mount – lid model

    Removing the lid from a solid block produces the most complex part of the mount:

    Bird Feeder Tray Mount - mount layout
    Bird Feeder Tray Mount – mount layout

    An aluminum plate on the outside (the gray slab in the overall view above) distributes the strain from the two M6 screws across the block.

    A smaller block on the inside of the lid has a pair of square nuts:

    Bird Feeder Tray Mount - segment layout
    Bird Feeder Tray Mount – segment layout

    All three parts build from their flattest side:

    Bird Feeder Tray Mount - build layout
    Bird Feeder Tray Mount – build layout

    The downward facing clamp arch in the main block didn’t need support, but the square nut sockets in the segment definitely came out better with little support blocks inside; PrusaSlicer does a good job with most support structures.

    The n-1 iteration used M6 rivnuts that were slightly too long after making the lid model match reality, so I switched to square nuts. The OpenSCAD code calculates the segment block length to match the actual screws, but 75 mm M6 screws and square nuts are barely long enough.

    I clamped the outer block to the lid as a drill guide for the first hole, then pinned the block with a screw to ensure it didn’t slip while drilling the second hole:

    Bird Feeder Tray Mount - drilling setup
    Bird Feeder Tray Mount – drilling setup

    Those were freehanded in the drill press at low speed with serious concentration; some things you just gotta do that way.

    The mixed flock overwhelmingly approves the trays, to the extent a dozen birds clamor to use them: definitely a crowd-pleaser!

    I’m certain you can buy pole-mounted trays, but what’s the fun in that?

    The OpenSCAD source code as a GitHub Gist:

    // Bird feeder tray mount
    // Ed Nisley – KE4ZNU
    // 2025-11-06
    include <BOSL2/std.scad>
    Layout = "Show"; // [Build,Show,Lid,Mount,Segment,Nut]
    /* [Hidden] */
    ID = 0;
    OD = 1;
    LENGTH = 2;
    HoleWindage = [0.2,0.2,0.2];
    Protrusion = 0.1;
    NumRibs = 12; // stiffening ribs
    NumSides = 8*NumRibs;
    $fn=NumSides;
    Gap = 5.0;
    WallThick = 9.0; // robust walls
    Kerf = 1.0; // clamp cut
    TapeThick = 0.5; // wrap around pole
    LidOD = 12; // main diameter in inches
    PoleOD = 1*INCH;
    PlateThick = 2.0; // backing plate for clamp
    Screw = [6.0,12.0,6.0]; // thread OD, washerOD, head
    ScrewLength = 75.0;
    ScrewOC = 60.0; // chosen to clear stiffening ribs in lid
    LidLayers = [ // bottom to top, ID = 0 means solid disk, LENGTH = exterior measurement
    [0,(LidOD-2*(3/8))*INCH,Protrusion], // 0 – below zero to prevent Z fighting
    [0,(LidOD-2*(3/8))*INCH,(3/8)*INCH], // 1 – base inside bucket
    [0,(LidOD+2*(1/8))*INCH,(1/8)*INCH], // 2 – flange
    [(LidOD-2*(1/2))*INCH,LidOD*INCH,(7/8)*INCH], // 3 – sealing ring
    ];
    LidOAH = LidLayers[1][LENGTH] + LidLayers[2][LENGTH] + LidLayers[3][LENGTH];
    LidTopDepth = (3/4)*INCH; // from highest part of interior
    MountBlockWidth = ScrewOC + 2*WallThick;
    BaseSagitta = LidLayers[1][OD]/2 – sqrt((LidLayers[1][OD]/2)^2 – (MountBlockWidth^2)/4);
    echo(BaseSagitta=BaseSagitta);
    PoleOffset = BaseSagitta + ((LidLayers[2][OD] – LidLayers[1][OD])/2) + WallThick + PoleOD/2;
    MountBlock = [PoleOffset + PoleOD/2 + WallThick – PlateThick,MountBlockWidth,LidOAH];
    echo(MountBlock=MountBlock);
    SegBlockOffset = ScrewLength – MountBlock.x – PlateThick; // assumes recessed
    SegmentBlock = [2*SegBlockOffset,MountBlock.y,LidTopDepth];
    Rib = [2*6.0,5.0,LidTopDepth]; // lid stiffening ribs
    RibAlign = 0 * 180/NumRibs; // position ribs wrt mount
    EdgeRadius = 3.0;
    //—–
    // Rivnut
    // The model collects all the magic numbers right here
    /*
    RivnutOAL = 15.0;
    module Rivnut() {
    union() {
    cyl(1.6,d=13.0,circum=true,anchor=BOTTOM);
    cyl(RivnutOAL,d=9.0,circum=true,anchor=BOTTOM);
    }
    }
    */
    //—–
    // Square nut
    // The model collects all the magic numbers right here
    NutOAL = 5.0;
    module SquareNut() {
    cuboid([10.0,10.0,5.0],anchor=BOTTOM);
    }
    //—–
    // Bucket lid
    // Centered at XY=0, Z=0 at top of exterior flange
    module BucketLid(Interior=true,Expand=false) {
    render()
    union() {
    down(LidLayers[2][LENGTH])
    cyl(LidLayers[1][LENGTH],d=LidLayers[1][OD],anchor=TOP);
    cyl(LidLayers[2][LENGTH],d=LidLayers[2][OD],anchor=TOP);
    if (Interior) {
    if (false)
    down(Expand ? Protrusion : 0)
    tube(LidLayers[3][LENGTH] + (Expand ? 2*Protrusion : 0),
    id=LidLayers[3][ID],od=(Expand ? 2 : 1)*LidLayers[3][OD],anchor=BOTTOM);
    else
    difference() {
    cyl(LidLayers[3][LENGTH] + (Expand ? 2*Protrusion : 0),
    d=(Expand ? 2 : 1)*LidLayers[3][OD],anchor=BOTTOM);
    up(LidLayers[3][LENGTH] – LidTopDepth)
    cyl(LidTopDepth + (Expand ? 2*Protrusion : 0),
    d=LidLayers[3][ID],anchor=BOTTOM);
    }
    up(LidLayers[3][LENGTH] – LidTopDepth)
    for (i=[0:(NumRibs – 1)])
    zrot(i*360/NumRibs + RibAlign)
    right(LidLayers[3][ID]/2)
    cuboid(Rib,anchor=BOTTOM,rounding=1,edges="Z");
    }
    else
    down(Expand ? Protrusion : 0)
    cyl(LidLayers[3][LENGTH] + (Expand ? 2*Protrusion : 0),
    d=(Expand ? 2 : 1)*LidLayers[3][OD],anchor=BOTTOM);
    }
    }
    // Mount clamp
    module Mount() {
    render()
    difference() {
    cuboid(MountBlock,anchor=BOTTOM+LEFT,rounding=EdgeRadius,edges="X");
    left(LidLayers[1][OD]/2 – BaseSagitta)
    up(LidLayers[1][LENGTH] + LidLayers[2][LENGTH])
    BucketLid(Interior=false);
    right(PoleOffset) {
    cyl(3*MountBlock.z,d=(PoleOD + HoleWindage.x + 2*TapeThick),circum=true,anchor=CENTER);
    cuboid([Kerf,2*MountBlock.y,3*MountBlock.z]);
    }
    if (false)
    right(MountBlock.x – PlateThick)
    cuboid(3*[PlateThick,MountBlock.y,MountBlock.z],anchor=LEFT);
    up(LidOAH – LidLayers[3][LENGTH]/2)
    for (j=[-1,1])
    fwd(j*ScrewOC/2) {
    cyl(ScrewLength,d=Screw[ID] + HoleWindage.x,circum=true,orient=RIGHT,anchor=BOTTOM,$fn=6,spin=180/6);
    if (false)
    right(MountBlock.x + Protrusion)
    cyl(Screw[LENGTH] + Protrusion,d=Screw[OD] + HoleWindage.x,circum=true,
    orient=LEFT,anchor=BOTTOM,$fn=12,spin=180/12);
    }
    }
    }
    // Nut block segment inside lid
    module NutSegment() {
    render()
    difference() {
    cuboid(SegmentBlock,anchor=BOTTOM,rounding=EdgeRadius,edges="X");
    down(LidLayers[3][LENGTH] – LidTopDepth)
    left(LidLayers[1][OD]/2 – BaseSagitta)
    BucketLid(Interior=true,Expand=true);
    up(LidTopDepth – LidLayers[3][LENGTH]/2)
    for (j=[-1,1])
    fwd(j*ScrewOC/2) {
    left(SegmentBlock.x/2)
    cyl(ScrewLength,d=Screw[ID],circum=true,anchor=BOTTOM,$fn=6,spin=180/6,orient=RIGHT);
    left(SegmentBlock.x/2)
    yrot(90)
    SquareNut();
    }
    }
    }
    //—–
    // Build things
    if (Layout == "Lid")
    BucketLid();
    if (Layout == "Mount")
    Mount();
    if (Layout == "Segment")
    NutSegment();
    if (Layout == "Nut")
    Rivnut();
    if (Layout == "Show") {
    down(LidLayers[1][LENGTH] + LidLayers[2][LENGTH]) {
    Mount();
    color("Orange",0.5)
    up(LidOAH – LidLayers[3][LENGTH]/2)
    right(MountBlock.x + PlateThick)
    for (j=[-1,1])
    fwd(j*ScrewOC/2)
    cyl(ScrewLength,d=Screw[ID],circum=true,orient=LEFT,anchor=BOTTOM);
    }
    up(LidLayers[3][LENGTH] – LidTopDepth)
    NutSegment();
    color("Gray",0.4)
    right(PoleOffset)
    cylinder(3*MountBlock.z,d=(PoleOD),anchor=CENTER);
    color("Gray",0.4)
    left(LidLayers[1][OD]/2 – BaseSagitta)
    BucketLid();
    color("White",0.7)
    down(LidLayers[1][LENGTH] + LidLayers[2][LENGTH])
    right(MountBlock.x + 2*PlateThick)
    difference() {
    cuboid([PlateThick,MountBlock.y,MountBlock.z],anchor=BOTTOM+LEFT,rounding=EdgeRadius,edges="X");
    up(LidOAH – LidLayers[3][LENGTH]/2)
    for (j=[-1,1])
    fwd(j*ScrewOC/2)
    cyl(ScrewLength,d=Screw[ID],circum=true,orient=RIGHT,anchor=CENTER);
    }
    }
    if (Layout == "Build") {
    render()
    union() {
    difference() {
    left(MountBlock.z + Gap/2)
    up(PoleOffset – Kerf/2)
    yrot(90)
    Mount();
    cuboid([3*MountBlock.z,2*MountBlock.y,3*MountBlock.x],anchor=TOP);
    }
    render()
    right(Gap/2)
    intersection() {
    up(MountBlock.x)
    yrot(90)
    Mount();
    up(MountBlock.x – PoleOffset)
    right(MountBlock.z/2)
    cuboid([2*MountBlock.z,2*MountBlock.y,MountBlock.x],anchor=TOP);
    }
    right(2*MountBlock.z – BaseSagitta)
    up(SegmentBlock.x/2)
    yrot(-90)
    NutSegment();
    }
    }

  • AC Power / Energy Meter

    AC Power / Energy Meter

    A surprisingly competent AC power-line voltage / current / energy / power meter fits neatly into a mud ring atop a 4×4 inch square electrical box:

    AC Power Meter - assembled
    AC Power Meter – assembled

    The inside view shows the wiring, such as it is:

    AC Power Meter - interior
    AC Power Meter – interior

    The square black block is the split-core current transformer around the hot line wire, which sticks up just enough in any orientation to require an extension ring, thus a second trip to the Big Box store.

    The mud ring has two tabs with threaded screw holes for the device (switch / GFCI / whatever): grab those with a Vise-Grip, flex until they break off, then file down the stub.

    Generous globs of hot-melt glue secure the meter in the mud ring. I added a strip of duct tape under the connections in the hope it might avert disaster should either of the AC wires come loose, but my real hope is in the safety ground to the metal box.

    The line cord comes from the Box o’ IEC cords, minus its IEC connector, plus the bright yellow USA-ian connector.

    Yes, the three metal box pieces and the Leviton connector cost far more than the meter.

    Not to code, but good enough for my purposes.

  • PolyDryer Box Desiccant Tray

    PolyDryer Box Desiccant Tray

    Having used desiccant in tea bags inside the PolyDryer boxes with some success, I wanted to see what happens with more exposed surface area:

    Polydryer Box desiccant tray - installed
    Polydryer Box desiccant tray – installed

    The tray (jawbreaker boxes.py URL) is 2 mm chipboard with a quartet of additional notches fitting the protrusions in the bottom of the Polydryer box:

    Polydryer Box desiccant tray - assembly
    Polydryer Box desiccant tray – assembly

    Although you’ll find plenty of printed trays, many with ingenious perforated lids, this was quick & easy:

    Polydryer Box desiccant tray - cutting
    Polydryer Box desiccant tray – cutting

    They’re painfully prone to dumping their contents, despite the dividers which are intended to dissuade the beads from taking collective action and surging over the slightly higher outer walls. Fortunately, the dump occurs inside a sealed box and is entirely survivable.

    Distributing 25 g of silica gel neatly fills the sections:

    Polydryer Box desiccant tray - top view
    Polydryer Box desiccant tray – top view

    Now it’s just a matter of time …

  • Dryer Vent Filter Snout: TPU Warp

    Dryer Vent Filter Snout: TPU Warp

    Making the clothes dryer vent filter snout from TPU did not work nearly as well as I expected:

    Clothes Dryer Vent Filter Snout - TPU warp
    Clothes Dryer Vent Filter Snout – TPU warp

    I think that’s the result of applying heat to a slightly compressed rear wall made of bendy plastic.

    Making it from much stiffer white PETG required moving the front mounting tabs to the middle to allow enough bendiness to snap them into the vent:

    Clothes Dryer Vent Filter Snout - OpenSCAD plan
    Clothes Dryer Vent Filter Snout – slicer

    Although both pieces barely fit on the MK4’s platform, I made the upper ring first to verify the fit:

    Clothes Dryer Vent Filter Snout - slicer
    Clothes Dryer Vent Filter Snout – slicer

    If I ever make another, it’ll print as a single top-side-down unit, because the dimensions are now spot on.

    From outside, it looks just like the TPU version:

    Clothes Dryer Vent Filter Snout - PETG installed
    Clothes Dryer Vent Filter Snout – PETG installed

    The snood is a cheesecloth tube with shock cord holding it to the snout.

  • Humidifier Caster Feet

    Humidifier Caster Feet

    The ancient and muchrepaired Sears humidifier works better in its new location across the living room with its front raised a few millimeters, which may have something to do with its plastic housing supporting a pair of heavy water containers for a few decades.

    After fiddling around with shims to find the proper height, these feet descended from the Husky workbench feet:

    Humidifier Caster Feet - installed
    Humidifier Caster Feet – installed

    They’re glued up from 3 mm plywood sitting on a 1 mm layer of cork:

    Humidifier Caster Feet - clamping
    Humidifier Caster Feet – clamping

    The humidifier seems much happier with its casters 4 mm above the floor. Seems awfully fussy to me, but there’s no arguing with success.

  • Tour Easy: Garage Door Remote Mount

    Tour Easy: Garage Door Remote Mount

    It turns out that keeping the garage door remote clipped to the starboard underseat pack on my Tour Easy attenuated its RF enough that even the directed receiver antenna couldn’t grab enough signal until I rolled onto the end of the driveway.

    While contemplating what’s involved in making a 3D model of the remote’s curved backside, I realized the bike already had a perfect spot:

    Tour Easy Zzipper Fairing - block mount
    Tour Easy Zzipper Fairing – block mount

    A few strips of good outdoor-rated foam tape later:

    Tour Easy - garage door opener mount
    Tour Easy – garage door opener mount

    Believe it or not, the camera is looking through the year-old and unwashed fairing on my bike.

    Stipulated: aligning the PCB antenna flat against a small aluminum plate atop a bunch of aluminum bars isn’t perfect. However, enough RF wriggles out to trigger our opener from four houses down the hill, giving it plenty of time to haul the door out of my way.

    That was trivial …