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: Home Ec

Things around the home & hearth

  • Dryer Vent Filter Snout: More Warping

    Dryer Vent Filter Snout: More Warping

    I have unfairly maligned the TPU snout, because the PETG snout failed the same way:

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

    Seen with the shock cord in place, it’s obvious that combining moderately high temperature with steady compression sufficed to bend the PETG enough to pop those tabs loose from the vent.

    So the OpenSCAD model now produces a stiffening ring to be laser-cut from acrylic:

    Clothes Dryer Vent Filter Snout - OpenSCAD stiffener
    Clothes Dryer Vent Filter Snout – OpenSCAD stiffener

    The whole snout builds as a single unit in the obvious orientation:

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

    Because the part of the snout with the tabs is 7 mm tall, I glued a 4 mm acrylic ring to a 3 mm ring, with both of them glued to the snout:

    Clothes Dryer Vent Filter Snout - acrylic gluing
    Clothes Dryer Vent Filter Snout – acrylic gluing

    That’s “natural” PETG, which I expected to be somewhat more transparent, but it’s definitely not a dealbreaker.

    Mary will sew up another cheesecloth filter and we’ll see what happens to this setup.

    As the saying goes, “Experience is what you get when you don’t get what you want.”

    Fortunately, living in the future makes it easy to iterate on the design & implementation until experience produces what should have been obvious at the start.

  • Garden Step2 Seat: Axle Repair

    Garden Step2 Seat: Axle Repair

    The cart in Mary’s Vassar Farm plot returned in need of repair:

    Garden Seat - fractured body
    Garden Seat – fractured body

    Those fractures near the end of the axle let the axle erode the side wall:

    Garden Seat - eroded body
    Garden Seat – eroded body

    This will obviously require some sort of reinforcement on the body holding the axle, but the first challenge involved getting the wheels off the axle:

    Garden Seat - axle cover
    Garden Seat – axle cover

    Some brute force revealed the hub covers snapped over an install-only locking fastener:

    Garden Seat - axle retaining clip
    Garden Seat – axle retaining clip

    More brute force cut those fasteners (a.k.a. star-lock washers) to get the wheels off the axles.

    While contemplating the situation, a box of 606 bearings (as used in the PolyDryer auto-rewind spindles) failed to scamper out of the way and produced a victim fitting perfectly on the 8 mm axle:

    Garden Seat - bearing idea
    Garden Seat – bearing idea

    I regard such happenstance as a message from the Universe showing I’m on the right track. The alert reader will note the axle should not rotate, but does sport scars showing it’s done some turning in the recent past, so the bearing may not be a completely Bad Idea™.

    Finding a Lexan snippet exactly as thick as the bearing suggested bolting a plate across the side of the body to support the bearing, like this:

    Garden Seat - reinforcing plate installed
    Garden Seat – reinforcing plate installed

    Some layout work in LightBurn produced a template to mark the body for hand-drilling the holes:

    Garden Seat - drill marking template
    Garden Seat – drill marking template

    In retrospect, that was a mistake. I should have:

    • Laser-cut an MDF sheet to make a drill jig
    • Drilled one hole and inserted a screw
    • Drilled the rest of the holes in exactly the right places

    Instead, three of the holes in that nice Lexan sheet ended up slightly egg-shaped to adjust for mis-drilled holes in the body.

    Lexan does not laser-cut well at all, so that sheet was drilled to suit after using the template to mark the holes:

    Garden Seat - plate drilling
    Garden Seat – plate drilling

    Then it got bandsawed / belt-sanded into shape.

    I squeezed 5 mm rivnuts into whatever fiber-reinforced plastic they used for the body, which worked better than I expected. They’re intended for sheet metal, so I set the tool for 5 mm compression and they seem secure. I hope using plenty of screws across a large plate will diffuse the stress on each screw.

    Then I threaded the axles and used acorn nuts:

    Garden Seat - repaired axle installed
    Garden Seat – repaired axle installed

    In this situation, I regard JB KwikWeld epoxy as “removable with some effort”, as opposed to the destruction required with those star-lock washers. High-strength Locktite might also be suitable, but I do not anticipate ever having to remove these again for any reason and do not want the nuts to fall off in the garden.

    The re-replaced seat conjured from a cafeteria tray continues to work fine, as do its 3D printed hinges.

    It’ll reside in the shed until Spring rolls around …

  • Sears Humidifier Bottle Cap Reinforcement

    Sears Humidifier Bottle Cap Reinforcement

    In the midst of the humidification season, I spotted this while refilling one of the ancient Sears Humidifier bottles:

    Humidifier bottle cap reinforcement - crack
    Humidifier bottle cap reinforcement – crack

    While it’s possible to buy replacement caps, this seemed more appropriate:

    Humidifier bottle cap reinforcement - installed
    Humidifier bottle cap reinforcement – installed

    It’s PETG-CF, of course:

    Bottle cap reinforcement - solid model
    Bottle cap reinforcement – solid model

    The shape is a ring with a simplified model of the cap removed from the middle:

    Bottle cap reinforcement - lid solid model
    Bottle cap reinforcement – lid solid model

    It fits snugly over the cap atop a thin layer of JB PlasticBonder that should hold it in place forevermore:

    Humidifier bottle cap reinforcement - bottom view
    Humidifier bottle cap reinforcement – bottom view

    The other side shows the crack over on the right:

    Humidifier bottle cap reinforcement - top view
    Humidifier bottle cap reinforcement – top view

    Close inspection showed a few smaller cracks, so that cap was likely an original.

    I made another ring for the other cap, only to find it was slightly larger with a black washer inside: apparently a previous owner had replaced one of the caps. The OpenSCAD program has measurements for both, not that you have either.

    The OpenSCAD source code as a GitHub Gist:

    // Humidifier bottle cap reinforcement
    // Ed Nisley – KE4ZNU
    // 2025-11-29
    include <BOSL2/std.scad>
    Layout = "Show"; // [Show,Build,Cap]
    /* [Hidden] */
    Protrusion = 0.1;
    //—–
    // Bottle cap/valve
    // Collects all the magic numbers in one place
    Left = false; // the caps are different, of course
    CapODs = Left ? [43.0,42.1] : [43.1,42.9]; // [0] = base of cap
    CapHeight = 10.0;
    Notch = [0.6,2.0,8.5 + Protrusion]; // Z + hack for slight angle
    NumRibs = 24;
    RibAngle = 90 – atan(CapHeight/((CapODs[0]-CapODs[1])/2));
    echo(RibAngle=RibAngle);
    $fn=2*NumRibs;
    module Cap() {
    difference() {
    cyl(CapHeight,d1=CapODs[1],d2=CapODs[0],anchor=BOTTOM);
    for (a=[0:NumRibs-1])
    zrot(a*360/NumRibs)
    right(CapODs[1]/2) down(Protrusion)
    yrot(RibAngle)
    cuboid(Notch,anchor=RIGHT+BOTTOM);
    }
    }
    //—–
    // Reinforcing ring
    RingThick = 3.0;
    module Ring() {
    render()
    difference() {
    tube(CapHeight,od=CapODs[0] + 2*RingThick,id=CapODs[1] – 2*Notch.x,anchor=BOTTOM);
    Cap();
    }
    }
    // Build things
    if (Layout == "Cap")
    Cap();
    if (Layout == "Build" || Layout == "Show")
    Ring();
  • Hens and Chicks Coasters

    Hens and Chicks Coasters

    Mary’s Hens and Chicks gardening group is having a White Elephant gift swap, where one can get rid of anything vaguely garden-related without repercussions, so I ran off a set of eponymous coasters for practice:

    Hens and Chicks Coasters - overview
    Hens and Chicks Coasters – overview

    They’re 3 mm laser plywood with English Chestnut stain and satin polyurethane sealant, with PSA cork on the underside. Even if (IMO) the stain came out too dark on some of them, they’re perfectly suited for the occasion.

    Coasters need a storage case:

    Hens and Chicks Coasters - case
    Hens and Chicks Coasters – case

    That’s 1.5 mm Trocraft Eco, which is AFAICT really nice chipboard, with a box layout from boxes.py.

    The image comes from The New Garden Encyclopedia, a fine source of classic images and outdated advice.

  • Large Smashed Glass Coaster

    Large Smashed Glass Coaster

    Those of long memory will recall our vermiculture setup in the basement that turns kitchen scraps into plant food. We accumulate scraps in plastic milk jugs, which jugs get recycled after they become grody.

    I finally made a decorative coaster to keep the sometimes-wet jug off the counter:

    Printed Fragment Coaster 165mm - in use
    Printed Fragment Coaster 165mm – in use

    This used several of the larger smashed glass fragments from the collection:

    Fragments 165mm square - scan sample
    Fragments 165mm square – scan sample

    They all fit inside a 165 mm square, with the conformal perimeter disguising the outline:

    Printed Fragment Coaster 165mm - overview
    Printed Fragment Coaster 165mm – overview

    I printed the frame with the same blue PETG-CF that leaked epoxy the last time around. Using the correct filament setting (Extrusion Multiplier = 1.0) produced an epoxy-tight frame:

    Printed Fragment Coaster 165mm - epoxy filling
    Printed Fragment Coaster 165mm – epoxy filling

    The overall process:

    • Run a bead of epoxy around the edge of each recess
    • Fill in the center with a thin layer
    • Squish the metallized paper reflector in place starting from one end to ease the bubbles out
    • Cover the reflector with another layer of epoxy
    • Lay the glass fragment down starting at one end
    • Press gently down to get all the bubbles out
    • Cover the glass with more epoxy

    I dripped enough epoxy on each fragment to form a meniscus without having it go over the rim:

    Printed Fragment Coaster 165mm - epoxy meniscus
    Printed Fragment Coaster 165mm – epoxy meniscus

    The Basement Shop temperature is just over 60 °F, so I put a heating pad in a huge ziplock bag, laid an aluminum sheet atop it as a heat spreader, put some waxed paper on the aluminum just in case, then did the filling described above:

    Printed Fragment Coaster 165mm - warming setup
    Printed Fragment Coaster 165mm – warming setup

    A cardboard box on top helped the heating pad keep the coaster at a uniform 85 °F, slightly warmer than the epoxy instructions recommend, but it cured overnight with a wonderfully shiny surface.

    Now that I have the process down, making glittery coasters is surprisingly easy.

  • 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();
    }
    }

  • 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.