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

  • Seedling Shelter Frame

    Seedling Shelter Frame

    Plant seedlings started in pots require some hardening off time outdoors before being transplanted. Veggie seedlings also require protection from critters regarding them as a buffet, so Mary covers them with a sheet of floating row cover, which must be both suspended over the plants to give them growing room and tucked under the tray to keep the bugs out. She asked for a frame to simplify the process:

    Mesh Shelter Frame - assembled
    Mesh Shelter Frame – assembled

    The solid model shows the structure with no regard for proportion:

    Mesh Shelter Frame - show view
    Mesh Shelter Frame – show view

    The 5 mm fiberglass rods come from our decommissioned six-passenger umbrella, cut to length in the Tiny Lathe™ by applying a Swiss Pattern knife file around the perimeter, over the ShopVac’s snout to catch the glass dust. I started with a pull saw (also over the vacuum) during the weekly Squidwrench v-meeting, whereupon Amber recommended either a Dremel slitting wheel or a file, so I mashed everything together and it worked wonderfully well, without producing any errant glass-fiber shards to impale my fingers.

    The corners consist of three tubes stuck together at the origin:

    Mesh Shelter Frame - non-hulled corner model
    Mesh Shelter Frame – non-hulled corner model

    Shrink-wrapping them with a hull() adds plenty of strength where it’s needed:

    Mesh Shelter Frame - hulled corner model
    Mesh Shelter Frame – hulled corner model

    I decided putting the belly side (facing you in the picture) downward on the platform and the peak upward would distribute the distortion equally among the tubes and produce a nicely rounded outer surface for the mesh fabric:

    Mesh Shelter Frame - build layout
    Mesh Shelter Frame – build layout

    Which led to some Wikipedia trawling to disturb the silt over my long-buried analytic geometry, plus some calculator work to help recall the process; back in the day I would have used a slipstick, but I was unwilling to go there. Although I could special-case this particular layout, the general method uses Euler’s Rotation Theorem, simplified because I need only one rotation.

    Should you need concatenated rotations, you probably need quaternions, but, at this point, I don’t even remember forgetting quaternions.

    Anyhow, the Euler rotation axis is the cross product of the [1,1,1] vector aimed through the middle of the corner’s belly with the [0,0,-1] target vector pointing downward toward the platform. The rotation amount is the acos() of the dot product of those two vectors divided by the product of their norms. With vector and angle in hand, dropping them into OpenSCAD’s rotate() transformation does exactly what’s needed:

    rotate(acos((BaseVector*Nadir)/(norm(BaseVector)*norm(Nadir))),
           v=cross(BaseVector,Nadir))   // aim belly side downward
      Corner();

    Dang, I was so happy when that worked!

    Because the corner model rotates around the origin where all three tube centerlines meet, the result puts the belly below the platform, pointed downward. The next step applies a translation to haul the belly upward:

    translate([ArmOAL,0,    // raise base to just below platform level
               ArmOC/sqrt(3) + (ArmRadius/cos(180/SocketSides))*cos(atan(sqrt(3)/2)) + Finagle])

    This happens in a loop positioning the four corners for printing, so the first ArmOAL as the X axis parameter translates the shape far enough to let four of them coexist around the origin, as shown above.

    The mess in the Z axis parameter has three terms:

    • Raise the centerline of the ends of the tubes to Z=0
    • Raise the rim of the tube to Z=0
    • Add a wee bit to make the answer come out right

    The 0.18 mm Finagle constant fixes things having to do with the hull() applied to miscellaneous leftover angled-circles-as-polygons approximations and leaves just a skin below the platform to be sheared off by a huge cube below Z=0, matching the corner bellies with the bottoms of the feet.

    Because the corners have awful overhangs, the results look a bit raggedy:

    Mesh Shelter Frame - corner underside
    Mesh Shelter Frame – corner underside

    That’s after knocking off the high spots with a grubby sanding sponge and making a trial fit. They look somewhat less grotendous in person.

    If we need another iteration, I’ll think hard about eliminating the overhangs by splitting the corner parallel to the belly, flipping the belly upward, and joining the pieces with a screw. What we have seems serviceable, though.

    The OpenSCAD source code as a GitHub Gist:

    // Mesh Shelter Frame for outdoor sprouts
    // Ed Nisley KE4ZNU – July 2020
    /* [Layout Options] */
    Layout = "Show"; // [Build, Show, Corner, CornerSet, Base, BaseSet]
    //——-
    //- Extrusion parameters must match reality!
    // Print with 2 shells
    /* [Hidden] */
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleFinagle = 0.2;
    HoleFudge = 1.00;
    function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;
    Protrusion = 0.1; // make holes end cleanly
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    inch = 25.4;
    //——-
    // Dimensions
    RodOD = 5.0;
    SocketDepth = 3*RodOD;
    WallThick = 3.0;
    ArmOD = RodOD + 2*WallThick;
    ArmRadius = ArmOD / 2;
    SocketSides = 3*4;
    ArmOC = SocketDepth + ArmOD; // rod entry to corner centerline
    ArmOAL = ArmOC + ArmRadius; // total arm length to outer edge
    echo(str("ArmOC: ",ArmOC));
    echo(str("ArmOAL: ",ArmOAL));
    Nadir = [0,0,-1]; // vector toward print platform
    RodLength = 100; // just for show
    //——-
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(r=HoleAdjust(FixDia)/2,h=Height,$fn=Sides);
    }
    //——-
    BaseVector = [1,1,1]; // vector through middle of base surface
    module Corner() {
    difference() {
    hull() {
    scale([1/cos(180/SocketSides),1/cos(180/SocketSides),1])
    rotate(180/SocketSides)
    sphere(d=ArmOD,$fn=SocketSides);
    rotate(180/SocketSides)
    cylinder(d=ArmOD,h=ArmOC,$fn=SocketSides);
    rotate([-90,0,0]) rotate(180/SocketSides)
    cylinder(d=ArmOD,h=ArmOC,$fn=SocketSides);
    rotate([0,90,0]) rotate(180/SocketSides)
    cylinder(d=ArmOD,h=ArmOC,$fn=SocketSides);
    }
    rotate(180/SocketSides)
    translate([0,0,ArmOD])
    PolyCyl(RodOD,SocketDepth + Protrusion,SocketSides);
    rotate([-90,0,0]) rotate(180/SocketSides)
    translate([0,0,ArmOD])
    PolyCyl(RodOD,SocketDepth + Protrusion,SocketSides);
    rotate([0,90,0]) rotate(180/SocketSides)
    translate([0,0,ArmOD])
    PolyCyl(RodOD,SocketDepth + Protrusion,SocketSides);
    }
    }
    module CornerSet(s=RodLength) {
    translate([-s/2,-s/2,s])
    mirror([0,0,1])
    Corner();
    translate([s/2,-s/2,s])
    rotate([0,0,90]) mirror([0,0,1])
    Corner();
    translate([s/2,s/2,s])
    rotate([0,0,180]) mirror([0,0,1])
    Corner();
    translate([-s/2,s/2,s])
    rotate([0,0,-90]) mirror([0,0,1])
    Corner();
    }
    module Base() {
    difference() {
    union() {
    cylinder(d=ArmOD,h=ArmOAL/2,$fn=SocketSides);
    resize([0,0,ArmOC/2])
    sphere(d=ArmOC,$fn=2*SocketSides);
    }
    translate([0,0,3*ThreadThick])
    PolyCyl(RodOD,ArmOAL,SocketSides);
    translate([0,0,-SocketDepth]) // cut sphere below platform
    cube(2*SocketDepth,center=true);
    }
    }
    module BaseSet(s=RodLength) {
    for (i=[-1,1], j=[-1,1])
    translate([i*s/2,j*s/2,0])
    Base();
    }
    //——-
    // Build it!
    if (Layout == "Corner")
    Corner();
    if (Layout == "CornerSet")
    CornerSet();
    if (Layout == "Base")
    Base();
    if (Layout == "BaseSet")
    BaseSet();
    if (Layout == "Show") {
    CornerSet();
    for (i=[-1,1])
    translate([i*RodLength/2,RodLength/2,RodLength])
    rotate([90,0,0])
    color("Green",0.5)
    cylinder(d=RodOD,h=RodLength,$fn=SocketSides);
    for (j=[-1,1])
    translate([RodLength/2,j*RodLength/2,RodLength])
    rotate([0,-90,0])
    color("Green",0.5)
    cylinder(d=RodOD,h=RodLength,$fn=SocketSides);
    BaseSet();
    for (i=[-1,1], j=[-1,1])
    translate([i*RodLength/2,j*RodLength/2,0])
    color("Green",0.5)
    cylinder(d=RodOD,h=RodLength,$fn=SocketSides);
    }
    if (Layout == "Build") {
    Finagle = 0.18; // hack for hull's angled round-to-polygon approximations, I think
    difference() { // slice sliver from base to sit flat on platform
    union()
    for (a=[45:90:360])
    rotate(a) // distribute around origin
    translate([ArmOAL,0, // raise base to just below platform level
    ArmOC/sqrt(3) + (ArmRadius/cos(180/SocketSides))*cos(atan(sqrt(3)/2)) + Finagle])
    rotate(17) // arbitrary rotation for tidy arrangement
    rotate(acos((BaseVector*Nadir)/(norm(BaseVector)*norm(Nadir))),
    v=cross(BaseVector,Nadir)) // aim belly side downward
    Corner();
    translate([0,0,-ArmOD/2]) // slicing block below platform
    cube([6*ArmOAL,6*ArmOAL,ArmOD],center=true);
    }
    rotate(45)
    for (i=[-1,1], j=[-1,1])
    translate([i*1.5*ArmOC,j*1.5*ArmOC,0])
    Base();
    }

  • If You See Something, Just Walk On By

    If You See Something, Just Walk On By

    Found another disconnected body part around Red Oaks Mill:

    Halloween Zombie Glove
    Halloween Zombie Glove

    I think it’s a zombie costume glove, but I’m just not going to get closer.

    Spotted on a walk. This is what happens when I leave the Basement Laboratory!

  • Monthly Science: Small Praying Mantis

    Monthly Science: Small Praying Mantis

    These Praying Mantis nymphs may have emerged from the ootheca I rescued from the grass trimming operation earlier this year:

    Praying Mantises in grass - 2020-07-24
    Praying Mantises in grass – 2020-07-24

    The closest one was about 60 mm long, with plenty of growing ahead in the next few months:

    Praying Mantis - 2020-07-24
    Praying Mantis – 2020-07-24

    A few days later, I spotted a smaller one, maybe 40 mm from eyes to cerci, hiding much deeper in the decorative grass clump. Given their overall ferocity, it was likely hiding from its larger sibs.

    They have also been stilting their way across the window glass and screens in search of better hunting grounds. My affixing their oothecae to another bush may have disoriented them at first, but they definitely know where their next meal comes from!

    Perhaps as a bonus, a Katydid appeared inside the garage, stuck to the side of a trash can that Came With The House™ long ago:

    Katydid
    Katydid

    I deported it outside, in hopes of increasing the world’s net happiness.

    The stickers covering the can say “WPDH: A Decade of Rock ‘n’ Roll”, suggesting they date back to 1986, ten years after (Wikipedia tells me) WPDH switched from country to rock. Neither genre did much for me, so I never noticed.

  • Reinforced QD Propane Adapter Tool

    Reinforced QD Propane Adapter Tool

    Having just emptied a propane tank while making bacon, I couldn’t find any of the wrench adapters I made to remove the QD adapter from the tank’s POL fitting. With memory of the broken garden valve wrench still fresh, I tweaked the solid model to include a trio of 1 mm music wire reinforcements:

    Propane QD Adapter Tool - reinforced - Slic3r
    Propane QD Adapter Tool – reinforced – Slic3r

    Holes that small require clearing with a 1 mm drill, after which ramming the wires in place poses no problem:

    Reinforced QD Adapter Tool - inserting wire
    Reinforced QD Adapter Tool – inserting wire

    Except for the one that got away:

    Reinforced QD Adapter Tool - errant wire
    Reinforced QD Adapter Tool – errant wire

    The music wire came from a coil and each snippet required gentle straightening; perhaps that one wasn’t sufficiently bar-straight.

    Anyhow, I printed two tools for that very reason:

    Reinforced QD Adapter Tool - side view
    Reinforced QD Adapter Tool – side view

    They’re now where I can’t miss ’em the next time I need them, although that’s not where the previous ones reside.

    The OpenSCAD source code as a GitHub Gist:

    // Propane tank QD connector adapter tool
    // Ed Nisley KE4ZNU November 2012
    // 2018-04-08 toss MCAD includes overboard
    // 2020-07-27 add reinforcing rods
    //- Extrusion parameters must match reality!
    // Print with about half a dozen perimeter threads and 50% infill
    ThreadThick = 0.25;
    ThreadWidth = 2.0 * ThreadThick;
    HoleWindage = 0.2;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    Protrusion = 0.1; // make holes end cleanly
    inch = 25.4;
    //———————-
    // Dimensions
    WrenchSize = (5/8) * inch; // across the flats
    WrenchThick = 10;
    NoseDia = 8.6;
    NoseLength = 9.0;
    LockDia = 12.5;
    LockRingLength = 1.0;
    LockTaperLength = 1.5;
    TriDia = 15.1;
    TriWide = 12.2; // from OD across center to triangle side
    TriOffset = TriWide – TriDia/2; // from center to triangle side
    TriLength = 9.8;
    NeckDia = TriDia;
    NeckLength = 4.0;
    RebarOD = 1.0; // music wire pin 1 mm = 39 mil
    RebarLength = WrenchThick + NeckLength + TriLength;
    //———————-
    // 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(r=(FixDia + HoleWindage)/2,
    h=Height,
    $fn=Sides);
    }
    //——————-
    // Build it…
    $fn = 4*6;
    union() {
    translate([0,0,(WrenchThick + NeckLength + TriLength – LockTaperLength – LockRingLength + Protrusion)])
    cylinder(r1=NoseDia/2,r2=LockDia/2,h=LockTaperLength);
    translate([0,0,(WrenchThick + NeckLength + TriLength – LockRingLength)])
    cylinder(r=LockDia/2,h=LockRingLength);
    difference() {
    union() {
    translate([0,0,WrenchThick/2])
    cube([WrenchSize,WrenchSize,WrenchThick],center=true);
    cylinder(r=TriDia/2,h=(WrenchThick + NeckLength +TriLength));
    cylinder(r=NoseDia/2,h=(WrenchThick + NeckLength + TriLength + NoseLength));
    }
    for (a=[-1:1]) {
    rotate(a*120)
    translate([(TriOffset + WrenchSize/2),0,(WrenchThick + NeckLength + TriLength/2 + Protrusion/2)])
    cube([WrenchSize,WrenchSize,(TriLength + Protrusion)],center=true);
    }
    for (a=[-1:1]) {
    rotate(a*120 + 60)
    translate([NoseDia/2,0,-Protrusion])
    PolyCyl(RebarOD,RebarLength,6);
    }
    }
    }

  • Half-Teaspoon Soldering

    Half-Teaspoon Soldering

    My favorite half-teaspoon measure hit the floor with a surprising sproing:

    Half-teaspoon soldering - broken
    Half-teaspoon soldering – broken

    The weld lasted far longer than anyone should own a spoon, I suppose, but it wasn’t much to begin with:

    Half-teaspoon soldering - sprung handle
    Half-teaspoon soldering – sprung handle

    Having had much the same thing happen to a measuring cup from the same set, I cleaned the back of the spoon and the front of the handle with a stainless steel wire brush in the Dremel and gingerly re-bent the handle to remove any inclination it might have to break free again:

    Half-teaspoon soldering - cleaned and rebent
    Half-teaspoon soldering – cleaned and rebent

    Some 60% silver solder (the formula evidently changed in the last few decades), nasty flux, and propane torch work produced a decent fillet:

    Half-teaspoon soldering - cooling
    Half-teaspoon soldering – cooling

    It looks a bit worse on the far side, but I’ll never tell.

    Rinse off the flux, wire-brush the joint, wash again, and it’s all good.

    I thought about excavating the resistance soldering gadget, but the torch was closer to hand and a bigger fillet seemed in order.

  • NPN RGB Astable Multivibrator Timing Adjustment

    NPN RGB Astable Multivibrator Timing Adjustment

    Back in the beginning of July, I replaced the NP-BX1 battery in the RGB Piranha astable multivibrator with a 18650 lithium cell and a USB charge controller, then watched it blink for the next two weeks on the first charge:

    Astable - 10 11 12 uF tweak - 027
    Astable – 10 11 12 uF tweak – 027

    However, the blinks looked … odd and some poking around with a Tek current probe showed the red and blue astables had locked together, so they blinked in quick succession. Alas, I don’t have a scope shot to prove it.

    I built all three astables with the same parts, figuring the normal tolerance of electrolytic caps would make the astables run at slightly different rates, which they did at first.

    This being a prototype, I just soldered a 1 µF cap onto the blue channel’s existing 10 µF cap:

    Astable - 11 uF cap - detail
    Astable – 11 uF cap – detail

    You can barely make out the top of the additional 2.2 µF cap on the red channel, through the maze of components; now, they definitely have different periods.

    Aaaand the scope shot to prove it:

    Astable NPN - 10 11 12 uF tweak - 10 mA-div
    Astable NPN – 10 11 12 uF tweak – 10 mA-div

    The bottom trace shows the battery current at 10 mA/div. The first pulse, over on the left, has the red and blue LEDs firing in quick succession with some overlap, but they separate cleanly for their next pulses.

    You don’t want to build a battery-powered astable from NPN transistors, because the 8 mA current between blinks is murderously high. In round numbers, each of the three LEDs blinks twice a second for 30 ms at 20 mA, so they average 3.6 mA, less than half the current required to keep the astables running between blinks. Over the course of 14 days, the circuit drew 11.6 mA × 336 hr = 3900 mA·h until the protection circuit shut it down.

    The lead photo shows a harvested 18650 cell, but I started with a known-good Samsung 18650 cell rated at 2600 mA·h at a 0.2C = 520 mA rate to 2.75 V. It’s comforting to see more energy trickling out at a 0.005C rate!

    I must conjure a holder with contacts for an 18650 cell, support for a trio of 2N7000 MOSFET astables, and some kind of weird spider with the RGB Piranha LED on the top. Even a harvested 18650 cell should last a couple of months with a much longer blink period (500 ms is much too fast), less LED current (this one is shatteringly bright), and a lower average current.

    And, yeah, I’ve been misspelling “Piranha” for a while.

  • Shuttles Board Game: Replacement Pegs

    Shuttles Board Game: Replacement Pegs

    For reasons not relevant here, I made replacement pegs for the Shuttles board game:

    Shuttles Game - solid model - Slic3r
    Shuttles Game – solid model – Slic3r

    Not the most challenging solid model I’ve ever conjured from the vasty digital deep, but 3D printing is really good for stuff like this.

    The OEM pegs have a hollow center, most likely to simplify stripping them from the injection mold, which I dutifully duplicated:

    Shuttles Game pegs - hollow - solid model
    Shuttles Game pegs – hollow – solid model

    It turns out the additional perimeter length inside the pegs requires 50% more printing time, far offsetting the reduced 10% infill. Given that each solid set takes just under an hour, I decided to lose half an hour of verisimilitude.

    I plunked a nice round cap atop the OEM peg’s flat end, but stopped short of printing & installing a round plug for the butt end.

    While the 3D printer’s hot, ya may as well make a bunch:

    Shuttles game pegs
    Shuttles game pegs

    Game on …

    The OpenSCAD source code as a GitHub Gist:

    Update: They’re a bit too large, so the Gist now produces tapered pegs.

    // Shuttles game pegs
    // Ed Nisley KE4ZNU – July 2020
    /* [Layout Options] */
    Layout = "Peg"; // [Build, Peg]
    Hollow = false;
    //——-
    //- Extrusion parameters must match reality!
    /* [Hidden] */
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    ID = 0;
    OD = 1;
    LENGTH = 2;
    //——-
    // Dimensions
    /* [Dimensions] */
    Peg = [4.0,7.5,26.0]; // overall length, including the rounded Cap
    Taper = 1.0;
    CapRadius = Peg[OD]/2;
    PegBaseLength = Peg[LENGTH] – CapRadius;
    NumPegs = [1,6]; // lay out in array
    ArrayCenter = [NumPegs[0] – 1,NumPegs[1] – 1] / 2;
    NumSides = 6*4;
    //——-
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(r=FixDia/2 + HoleWindage,h=Height,$fn=Sides);
    }
    //——-
    // One peg
    module Peg() {
    union() {
    translate([0,0,PegBaseLength])
    difference() {
    sphere(d=Peg[OD],$fn=NumSides);
    translate([0,0,-Peg[OD]/2])
    cube([2*Peg[OD],2*Peg[OD],Peg[OD]],center=true);
    }
    difference() {
    cylinder(d1=Peg[OD] – Taper,d2=Peg[OD],h=PegBaseLength,$fn=NumSides);
    if (Hollow)
    translate([0,0,-Protrusion])
    PolyCyl(Peg[ID],PegBaseLength+Protrusion,NumSides);
    }
    }
    }
    //——-
    // Build it!
    if (Layout == "Peg")
    Peg();
    if (Layout == "Build")
    for (i=[0:NumPegs[0] – 1], j=[0:NumPegs[1] – 1])
    translate([(i – ArrayCenter.x)*1.5*Peg[OD],(j – ArrayCenter.y)*1.5*Peg[OD],0])
    Peg();