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

  • OpenSCAD AI Job Opening

    OpenSCAD AI Job Opening

    An email arrived:

    I’m reaching out because your teaching of practical solid modelling for 3D printing and native model exercises and documented custom enclosures and fixtures appear relevant to the OpenSCAD Developer opportunity listed by micro1.

    The work involves creating and modifying parametric 2D and 3D CAD models in OpenSCAD, troubleshooting designs, and explaining modelling workflows step by step. The listing describes a remote contractor role in eligible countries.

    If this sounds interesting, you can review the full requirements and apply through the link below. micro1 will assess applications and confirm suitability.

    The company describes itself thusly:

    micro1 is a data lab that helps AI labs & Enterprises train foundational models and agents.

    Our AI recruiting agent sources and vets domain experts for our data platform that enables rapid production of high-quality training data.

    The job description includes:

    In this role, you will use OpenSCAD to complete real-world 2D and 3D CAD modeling tasks and explain your workflows step by step. Your expertise will help improve how AI systems understand CAD design and parametric modeling.

    Among other responsibilities:

    • Complete advanced OpenSCAD tasks and explain your workflows step by step.
    • Create clear instructions and detailed prompts for CAD modeling workflows.

    With various “required” and “preferred” qualifications:

    • Access to OpenSCAD and ability to record your screen while completing tasks.
    • English proficiency at B2 level or higher.
    • A relevant master’s degree or PhD in Mechanical Engineering, Product Design, or a related field.
    • Previous Human Data experience, such as data annotation, RLHF, or AI model evaluation.

    All this for only:

    $65 – $123/hour pay

    After pondering their offer for a few hundred milliseconds, I realized I’m (fortunately) not in a situation where I must become a reverse centaur who allows monitoring my PC while I fix their mistakes.

    Anyway, I betcha their OpenSCAD AI already makes models like mine:

    Handlebar Grip Mount - sphere joint - solid model
    Handlebar Grip Mount – sphere joint – solid model

    More’s the pity, eh?

  • Delta Shower Head Holder: Now With Rotation

    Delta Shower Head Holder: Now With Rotation

    We decided a few more inches of hose would be useful, so the shower head holder now has a 90° rotation in the middle to straighten the hose from plumbing to the side clip holding it out of the way:

    Delta show head holder - installed rotated
    Delta show head holder – installed rotated

    The original rectangular connection becomes a simple square:

    Delta shower head holder extension - rotated - solid model
    Delta shower head holder extension – rotated – solid model

    Unlike the original version, however, building this one requires a support structure:

    Delta shower head holder extension - PrusaSlicer support
    Delta shower head holder extension – PrusaSlicer support

    Which turned out to be a Good Thing™, because a new cheap textured PEI steel printing plate seems to have poor adhesion despite being scrupulously clean:

    Delta shower head - poor print adhesion
    Delta shower head – poor print adhesion

    Apart from obvious defects due to the shifts while printing, the holder sufficed for acceptance testing.

    I’m not sure about that steel plate, though.

    The OpenSCAD source code as a GitHub Gist, which also contains the non-rotated version:

    // Delta shower head holder extension
    // Ed Nisley – KE4ZNU
    // 2025-05-02
    // 2026-11-09 add rotation for hose repositioning
    include <BOSL2/std.scad>
    Layout = "Show"; // [Show,Build,Plug,Socket,Connector,Retainer,Latch,Test]
    MountAngle = 30; // between OEM and new holder
    MountOffset = 40.0;
    BaseAngle = 90; // [90,0,-90]
    /* [Hidden] */
    HoleWindage = 0.2;
    Protrusion = 0.1;
    NumSides = 3*3*4;
    Gap = 5.0;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    WallThick = 8.0; // holder wall thickness
    Radius = 3.0; // shapely rounding
    Insert = [21.0,24.5,35.0]; // hose connector taper
    PlugSize = Insert + [-0.3,-0.3,0]; // … for better fit in OEM holder
    Slot = [Insert[OD],14.6,3*Insert[LENGTH]]; // slot on OEM holder, arbitrary length
    Latch = [Insert[OD] – 0.25,Insert[OD] + 2*WallThick,1.5]; // ID = latch engagement
    RetainerSize = [14.5,25.0,6.0];
    RetainerPegThick = 3.0;
    $fn=NumSides;
    //———-
    // Define Shapes
    module Plug() {
    up(Slot.y/2)
    xrot(BaseAngle)
    down(Slot.y/2)
    difference() {
    cyl(l=Insert[LENGTH],d1=PlugSize[ID],d2=PlugSize[OD],anchor=BOTTOM);
    down(Protrusion)
    cyl(10.0 + 5,d=5.5,anchor=BOTTOM); // M4 burly insert
    }
    }
    module LatchRing() {
    difference() {
    union() {
    tube(Latch[LENGTH],id=Insert[OD],od=Latch[OD],anchor=BOTTOM);
    difference() {
    tube(Latch[LENGTH]/2,id1=Insert[OD],id2=Latch[ID],od=Insert[OD],anchor=BOTTOM)
    position(TOP)
    tube(Latch[LENGTH]/2,id1=Latch[ID],id2=Insert[OD],od=Insert[OD],anchor=BOTTOM);
    cuboid(Slot + [0,1.0,0],anchor=RIGHT+CENTER);
    }
    }
    cuboid(Slot + [0,1.0,0],anchor=LEFT+CENTER);
    right(Insert[OD]/2)
    cube([Insert[OD],Insert[OD] + 2*WallThick,3*Insert[LENGTH]],anchor=LEFT+CENTER);
    }
    }
    module Socket() {
    difference() {
    tube(l=Insert[LENGTH],id1=Insert[ID],id2=Insert[OD],wall=WallThick,anchor=BOTTOM)
    position(TOP) LatchRing();
    cuboid(Slot + [0,1.0,0],anchor=LEFT+CENTER);
    right(Insert[OD]/2)
    cube([Insert[OD],Insert[OD] + 2*WallThick,3*Insert[LENGTH]],anchor=LEFT+CENTER);
    }
    }
    module Connector() {
    difference() {
    left(MountOffset – Insert[ID]/4)
    cuboid([MountOffset + Insert[LENGTH]*sin(MountAngle),Slot.y,Slot.y + 0*Insert[LENGTH]*cos(MountAngle)],
    anchor=LEFT+BOTTOM);
    left(MountOffset)
    down(Protrusion)
    cyl(10.0 + 5,d=5.5,anchor=BOTTOM); // M4 burly insert
    yrot(MountAngle) right(Insert[ID]/2 + WallThick)
    cyl(l=Insert[LENGTH],d1=Insert[ID] + 2*WallThick,d2=Insert[OD] + 2*WallThick,anchor=BOTTOM);
    }
    }
    module Retainer() {
    difference() {
    cyl(RetainerSize[LENGTH] – RetainerPegThick,d=RetainerSize[OD],anchor=BOTTOM) position(TOP)
    cyl(RetainerPegThick,d=RetainerSize[ID],anchor=BOTTOM);
    down(Protrusion)
    cyl(2*RetainerSize[LENGTH],d=4.0,anchor=BOTTOM);
    }
    }
    module Adapter() {
    union() {
    left(MountOffset)
    Plug();
    yrot(MountAngle) right((Insert[ID] + 2*WallThick)/2)
    Socket();
    Connector();
    }
    }
    //———-
    // Build things
    if (Layout == "Plug")
    Plug();
    if (Layout == "Socket")
    Socket();
    if (Layout == "Retainer")
    Retainer();
    if (Layout == "Connector")
    Connector();
    if (Layout == "Latch")
    LatchRing();
    if (Layout == "Test")
    render()
    difference() {
    left(0.75*Insert[LENGTH])
    up(Insert[OD]/2)
    yrot(90)
    Socket();
    cuboid(2*(Insert[OD] + 2*WallThick),anchor=RIGHT);
    }
    if (Layout == "Show") {
    Adapter();
    if (BaseAngle)
    back(sign(BaseAngle)*(RetainerSize[LENGTH] + Slot.y/2 + Gap))
    up(Slot.y/2)
    left(MountOffset)
    xrot(BaseAngle)
    Retainer();
    else
    left(MountOffset) down(RetainerSize[LENGTH] + Gap)
    Retainer();
    }
    if (Layout == "Build") {
    up(Insert[ID]/2 + WallThick + Insert[OD]/2)
    yrot(90-MountAngle)
    Adapter();
    right(Insert[LENGTH] + Insert[OD])
    Retainer();
    if (false)
    right(Insert[LENGTH] + 5.0) // fit test piece
    difference() {
    left(0.75*Insert[LENGTH])
    up(Insert[OD]/2)
    yrot(90)
    Socket();
    cuboid(2*(Insert[OD] + 2*WallThick),anchor=RIGHT);
    }
    }
    // Delta shower head holder extension
    // Ed Nisley – KE4ZNU
    // 2025-05-02
    include <BOSL2/std.scad>
    Layout = "Show"; // [Show,Build,Plug,Socket,Connector]
    MountAngle = 30; // between OEM and new holder
    MountOffset = 20.0;
    /* [Hidden] */
    HoleWindage = 0.2;
    Protrusion = 0.1;
    NumSides = 3*3*4;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Insert = [21.0,24.5,35.0]; // hose connector taper
    PlugSize = Insert + [-0.3,-0.3,0]; // … for better fit in OEM holder
    Slot = [Insert[OD],14.6,3*Insert[LENGTH]]; // slot on OEM holder, arbitrary length
    WallThick = 8.0; // holder wall thickness
    Radius = 3.0; // shapely rounding
    $fn=NumSides;
    //———-
    // Define Shapes
    module Plug() {
    cyl(l=Insert[LENGTH],d1=PlugSize[ID],d2=PlugSize[OD],anchor=BOTTOM);
    }
    module Socket() {
    difference() {
    tube(l=Insert[LENGTH],id1=Insert[ID],id2=Insert[OD],wall=WallThick,anchor=BOTTOM);
    cuboid(Slot + [0,1.0,0],anchor=LEFT+CENTER);
    right(Insert[OD]/2)
    cube([Insert[OD],Insert[OD] + 2*WallThick,3*Insert[LENGTH]],anchor=LEFT+CENTER);
    }
    }
    module Connector() {
    difference() {
    left(MountOffset)
    cuboid([MountOffset + Insert[LENGTH]*sin(MountAngle),Slot.y,Insert[LENGTH]*cos(MountAngle)],
    anchor=LEFT+BOTTOM);
    yrot(MountAngle) right(Insert[ID]/2 + WallThick)
    cyl(l=Insert[LENGTH],d1=Insert[ID] + 2*WallThick,d2=Insert[OD] + 2*WallThick,anchor=BOTTOM);
    }
    }
    module Adapter() {
    union() {
    left(MountOffset)
    Plug();
    yrot(MountAngle) right((Insert[ID] + 2*WallThick)/2)
    Socket();
    Connector();
    }
    }
    //———-
    // Build things
    if (Layout == "Plug")
    Plug();
    if (Layout == "Socket")
    Socket();
    if (Layout == "Connector")
    Connector();
    if (Layout == "Show")
    Adapter();
    if (Layout == "Build")
    up(Insert[ID]/2 + 1*WallThick + Insert[OD]/2)
    yrot(90-MountAngle)
    Adapter();
  • Craftsman Narrow Spring Clamp Jaws

    Craftsman Narrow Spring Clamp Jaws

    Quite some years ago I (ab)used several Craftsman (I’ve had them a long time) long narrow spring clamps while calibrating some thermocouples, much to the detriment of their rubbery jaws. The original gasket compound hackery didn’t work well, but eventually some of it stuck and I’ve been using them ever since:

    Craftsman Long Spring Clamp Jaw Pad - gasket compound
    Craftsman Long Spring Clamp Jaw Pad – gasket compound

    Now that I can 3D print TPU, however, it was time to fit better jaws in those things.

    The solid model eventually looked like this:

    Craftsman Long Spring Clamp Jaw Pads - solid model detail
    Craftsman Long Spring Clamp Jaw Pads – solid model detail

    The OpenSCAD code is replete with juicy magic numbers based on what few measurements I could make, then tweaked to fit the jaw where it had to go:

    <<< snippage >>>
    
    NumPads = 2 * 4;
    
    PadThick = 1.3;
    TabHeight = 1.5;
    
    module Tab() {
    
      difference() {
        linear_extrude(TabHeight)
          trapezoid(h=4.5,w1=1.4,w2=1.2,rounding=0.2,anchor=FRONT);
        back(5.5)
          xrot(60)
            cuboid([5,5,10],anchor=FRONT);
      }
    }
    <<< snippage >>>
    
    if (Layout == "Build") {
    nx = ceil(sqrt(NumPads));
    ny = nx;
    
    for (nx = [0:nx-1], ny = [0:ny-1])
      right(nx*Gap)
        back(ny*Gap)
          union() {
            linear_extrude(PadThick)
              trapezoid(h=7.5,w1=5.2,w2=4.2,rounding=0.2,anchor=FRONT);
            up(PadThick)
              back(0.5)
                Tab();
        }
    }
    
    

    The results is basically too small for the Prusa MK4 with the 0.8 mm nozzle laying down 0.4 mm layers, but the Makergear M2 with its 0.35 mm nozzle was in the midst of a heart attack at the time (about which, more later).

    The PrusaSlicer preview shows the chunky result:

    Craftsman Long Spring Clamp Jaw Pad - PrusaSlicer 0.8mm nozzle × 0.4mm layer detail
    Craftsman Long Spring Clamp Jaw Pad – PrusaSlicer 0.8mm nozzle × 0.4mm layer detail

    Because they’re so small, I printed nine at a time to get reasonable cooling and repair all four clamps.

    A few iterations produced a workable size & shape:

    Craftsman Long Spring Clamp Jaw Pad - size trials vs final install
    Craftsman Long Spring Clamp Jaw Pad – size trials vs final install

    The nicely curved profile comes from gluing them in place while gripping sturdy closed-cell foam:

    Craftsman Long Spring Clamp Jaw Pad - foam clamping
    Craftsman Long Spring Clamp Jaw Pad – foam clamping

    I used E6000+ adhesive, mostly because the tube didn’t scamper out of the way, but classic E6000 with nasty solvents might be Even Better. Neither would bond to the clamp’s plastic, so the eventual repair will once again require prying out debris with a dental pick.

    After resucitating the M2 , I resliced the model with 0.2 mm layers:

    Craftsman Long Spring Clamp Jaw Pad - PrusaSlicer 0.4mm nozzle × 0.2mm layer - detail
    Craftsman Long Spring Clamp Jaw Pad – PrusaSlicer 0.4mm nozzle × 0.2mm layer – detail

    Which looks much prettier in real life:

    Craftsman Long Spring Clamp Jaw Pad - detail - 0.8 vs 0.4 mm nozzle
    Craftsman Long Spring Clamp Jaw Pad – detail – 0.8 vs 0.4 mm nozzle

    A small ziplock bag with those nine pretty jaws now hangs from the rail supporting Too Many Clamps against future need.

  • Tiny T-Rex: Leg Modification

    Tiny T-Rex: Leg Modification

    Having recently built a herd of Tiny T-Rexes from cardboard, I finally discovered they didn’t stand quite right because their feet don’t reach the ground.

    The smaller orange outline is the original foot:

    Tiny T-Rex - 100mm - foot comparison
    Tiny T-Rex – 100mm – foot comparison

    The larger red outline came from a bit of node editing:

    Tiny T-Rex - 100mm - reshaped foot
    Tiny T-Rex – 100mm – reshaped foot

    I tweaked the original body to include the realigned foot shape to ensure the feet don’t get pried apart, as we’re not bound by anatomical accuracy.

    None of which matters for cardboard bonded with copious amounts of hot-melt glue, but I had smaller plans:

    Acrylic Tiny Rexes
    Acrylic Tiny Rexes

    The center piece is 6 mm translucent gray acrylic from the longsuffering IBM Printer Stand, with armor from 3 mm yellow on the left and a mix of 3 mm green / 1.5 mm transparent green on the right, held together with 3M 300LSE adhesive sheet.

    They are insufferably cute:

    Acrylic Tiny Rex - detail
    Acrylic Tiny Rex – detail

    Red eyes, because why not?

  • Laser Cuttery: Expedient Material Support

    Laser Cuttery: Expedient Material Support

    A remnant of acrylic looked like it would come in handy, but had a bent edge tall enough to collide with the laser nozzle. I eventually realized I could cut it with the edge downward, completely out of the way:

    Laser cutting - cardboard supports - side view
    Laser cutting – cardboard supports – side view

    The supports are cardboard strips sliced from the cardboard stash with a razor knife and hand-bent specifically to avoid recursive yak shaving:

    Laser cutting - cardboard supports - top view
    Laser cutting – cardboard supports – top view

    The cut was a simple straight line extending a bit beyond each end, using a bending mark to align the piece with the cut path. The scrap dropped free as the laser cleared the right end.

    Nothing fancy, but it worked exactly right the first time.

  • Furnace Pipe Temperature Sensor Mount: Diluting the Phlogiston

    Furnace Pipe Temperature Sensor Mount: Diluting the Phlogiston

    Being that type of guy, I enjoy keeping track of the gas furnace’s behavior and the least invasive method is monitoring the inlet and outlet water temperatures. I already have an Ambient Weather station for other things, including a pair of WH31P temperature sensor probes for the heat pump’s outdoor exhaust air and indoor duct temperature. Unfortunately, those sensors top out at (an undocumented in the description) 140 °F and the furnace water runs around 180 °F.

    Because I’m more interested in furnace run time than exact water temperature, putting the sensor somewhere between the 180 °F pipe and the 60 °F basement air seemed reasonable.

    A quick test showed it might work:

    Furnace pipe sensor - proof of concept
    Furnace pipe sensor – proof of concept

    That’s a random cart coin for insulation under a random heatsink for cooling, with the WH31P probe clamped under a snippet of closed-cell packing foam.

    Emboldened by 180 °F water in the pipe producing 80 °F at the probe, I conjured a more formal mount from the vasty digital deep:

    Furnace Pipe Sensor Mount - solid model - show view
    Furnace Pipe Sensor Mount – solid model – show view

    Which looks pretty much like that in real life:

    Furnace pipe sensor - installed
    Furnace pipe sensor – installed

    A layer of heatsink tape holds it to the plastic block, which in turn just sits on the rather crusty pipe surface. The blue wire is a thermocouple reporting the actual pipe temperature and, if the sensor isn’t getting enough heat, a blob of heatsink grease on the pipe will surely help.

    The spring arms originally held the heatsink to a PCB atop a Northbridge chip (or some such), so those solderless connectors crimped on shock cord should make it feel at home.

    Installing the sensors immediately raised the outdoor temperature by 20 °F at the beginning of an atypically warm November week, so it’ll be a while before I have any results.

  • Bee Tunnel Nests: End of Season

    Bee Tunnel Nests: End of Season

    Most of the tenants in the bee tunnel nests have departed:

    Bee Tunnel Nests - Nov 2026 - small A
    Bee Tunnel Nests – Nov 2026 – small A

    I assume the end caps with holes mark successful nests:

    Bee Tunnel Nests - Nov 2026 - small B
    Bee Tunnel Nests – Nov 2026 – small B

    Some bees overwinter in the nests, so they should emerge from the tunnels with intact end caps:

    Bee Tunnel Nests - Nov 2026 - small C
    Bee Tunnel Nests – Nov 2026 – small C

    Very few of the larger bamboo tubes met the acceptance criteria:

    Bee Tunnel Nests - Nov 2026 - large
    Bee Tunnel Nests – Nov 2026 – large

    Other critters seem to have used the tubes for various purposes, though, so they didn’t go to waste.

    I put these up in late April, with the June and July results showing much of the nesting happens shortly thereafter. Next year I should install the tubes in March; there’s no shortage of bamboo up the hill!