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

  • Garden Soaker Hose Repairs In Use

    Garden Soaker Hose Repairs In Use

    Just for completeness, here’s what the various soaker hose clamps look like in the garden, as solid models only let you visualize the ideal situation:

    Soaker Hose Connector Clamp - Show view
    Soaker Hose Connector Clamp – Show view

    This one prevents a puddle in the path to the right:

    Soaker hose repairs in situ - clamp
    Soaker hose repairs in situ – clamp

    Bending the hoses around the end of a bed puts them on edge, with this clamp suppressing a shin-soaking spray to the left:

    Soaker hose repairs in situ - end-on clamp
    Soaker hose repairs in situ – end-on clamp

    The clamp at the connector closes a leak around the crimped brass fitting, with the other two preventing gouges from direct sprays into the path along the bottom of the picture:

    Soaker hose repairs in situ - clamps and connector fix
    Soaker hose repairs in situ – clamps and connector fix

    All in all, a definite UI improvement!

    As far as I can tell, we have the only soaker hose repairs & spritz stoppers in existence. Hooray for 3D printing!

  • Dehumidifier Scrapping

    Dehumidifier Scrapping

    Dutchess County has another Household Hazmat / Electronics Disposal Day coming up, so I harvested some useful parts from the three dead dehumidifiers lurking under the bench.

    The (perfectly good) blower motor in one unit lives inside a convenient plastic housing:

    Scrap Dehumidifier Blower Motor - housing
    Scrap Dehumidifier Blower Motor – housing

    It’s sitting on three foam pads hot-melt glued to three wood blocks cut to fit inside three convenient molded features, making it nice & quiet & stable.

    The motor uses a nice polypropylene run capacitor:

    Scrap Dehumidifier Blower Motor - 6 uF cap
    Scrap Dehumidifier Blower Motor – 6 uF cap

    Which is also perfectly good:

    Scrap Dehumidifier Blower Motor - 6 uF cap test
    Scrap Dehumidifier Blower Motor – 6 uF cap test

    The motor includes a wiring diagram:

    Scrap Dehumidifier Blower Motor - wiring diagram
    Scrap Dehumidifier Blower Motor – wiring diagram

    I lashed it together with a chopped-off IEC cord, because the stock dehumidifier cords are just way too stiff. The motor and blower originally pulled air through the dust filter, the condenser, and the evaporator, before blowing it out the side, so it’s running pretty much unloaded. A quick test shows there’s not much difference between the high and low speeds:

    • High: 1050 RPM, 80 W, 12.5 m/s air flow
    • Low: 1000 RPM, 77 W, 11.7 m/s air flow

    Low speed seems slightly less noisy, but the wiring now has insulated QD connectors just in case I ever want to run it at full speed.

    For whatever it’s worth, the most recent dehumidifier failed one year into a two year warranty, but the company decided it was simpler to just refund the purchase price than to replace the unit. It seems the “sealed system” inside loses its refrigerant after a year and there’s no practical way to seal a small leak and recharge the system; unlike an automotive air conditioner, the tubes are soldered shut after the initial charge.

    They all sport Energy Star badges, but throwing away the whole damned thing every year or two tells me we’re not measuring the right values. Obviously, somebody could make a worthwhile dehumidifier, but as of now Frigidare, GE Appliances (sold to Haier), and Danby are on my shit list. Next year, I expect to add HomeLabs to the list, because the dehumidifier is identical to the Danby unit (and, ah-ha comes with a 2.5 year warranty). They’re all made by Haier (or another Chinese factory) and nobody applies any long-term QC to their products.

  • Painting By Numbers, Redux

    Painting By Numbers, Redux

    Five years later, the digits I painted with Rust-Oleum Rusty Metal Primer have weathered pretty well, while the original ink has fallen off the retroreflective sticker:

    Mailbox numbers - original vs primer
    Mailbox numbers – original vs primer

    As before, I wiped off the crud with denatured alcohol and painted neatly inside the lines. The other digits on both sides still look as good as the day I painted them, with only a few bubbles and nicks.

    Memo to self: Next time, buy a big sheet of 3M retroreflective film, make a stencil by vinyl cutting, paint the entire number in one shot, and be done with it.

  • Drill Press Vise Table Refresh

    Drill Press Vise Table Refresh

    I built a small plywood work table for the drill press:

    Drill press - scarred vise table
    Drill press – scarred vise table

    Obviously, that was a long time ago. It’s a plywood scrap with a small cleat screwed to its bottom, upon which one can position / clamp / hold / finagle smallish workpieces without worrying about drilling into the surface.

    The most recent batch of aluminum backing plates prompted me to finally replace that relic:

    Drill press - new vise table
    Drill press – new vise table

    The mill vise under the plywood grips the cleat and the whole affair rides on a Sears “Drill Press Milling Attachment Stock No 27585” which is basically a simple XY table with hand dials. It’s not rigid enough for actual milling (which you should never do on a drill press, anyway, because the end mill will pull itself out of the Jacobs chuck), but it’s good for tweaking the position before you drill something.

    One should never hand-hold workpieces while drilling.

    Don’t do as I do, do as I say. OK?

  • Soaker Hose Clamps

    Soaker Hose Clamps

    Having figured out the geometry for two- and three-channel soaker hoses, I cranked out more clamps:

    Soaker Hose Clamps - production
    Soaker Hose Clamps – production

    Actually, those are the remainder of two production runs devoted to reducing the amount of water sprinkling the garden paths. A 50 foot hose runs along both sides of one 14 foot bed, crosses the path, then continues along the adjacent bed. The hoses have (deliberate!) sprinkler holes along their porous rubber body and sometimes the layout puts a hole where it waters the path.

    The blue silicone rubber strips provide a bit of sealing to prevent the absurdly high pressure water from streaming through the orange PETG clamps. It’s OK if the clamp leaks, but less flow is better!

    I’m getting really good at making those aluminum backing plates and, in fact, I think it’s faster to run the blanks past the disk sander, then drill the holes, than to CNC-machine them. Could be wrong, but Quality Shop Time is not to be sniffed at.

  • Solid Modeling: Support Puzzle

    Solid Modeling: Support Puzzle

    I’ve been putting this type of support structure inside screw holes & suchlike for years:

    Browning Hi-Power Magazine Block - solid model - Generic 1 - support detail
    Browning Hi-Power Magazine Block – solid model – Generic 1 – support detail

    It’s basically a group of small rectangles rotated around the hole’s axis and about one thread thickness shorter than the overhanging interior.

    I’ve found that incorporating exactly the right support structure eliminates Slic3r’s weird growths, eases removal, and generally works better all around.

    So doing this for the baseplate of the Glass Tile frame came naturally:

    Glass Tile Frame - octagonal support
    Glass Tile Frame – octagonal support

    This OpenSCAD snippet plunks one of those asterisks in each of four screw holes:

      if (Support)
        color("Yellow")
          for (i=[-1,1], j=[-1,1])
            translate([i*InsertOC.x/2,j*InsertOC.y/2,0])
              for (a=[0:45:135])
                  rotate(a)
                    translate([0,0,(Screw[LENGTH] - ThreadThick)/2])
                      cube([Screw[OD] - 2*ThreadWidth,2*ThreadWidth,Screw[LENGTH] - ThreadThick],center=true);

    The “cubes” overlap in the middle, with no completely coincident faces or common edges, so it’s 2-manifold. Slic3r, however, produces a weird time estimate whenever the model includes those structures:

    Slic3r - NaN time estimate
    Slic3r – NaN time estimate

    NaN stands for Not A Number and means something horrible has happened in the G-Code generation. Fortunately, the G-Code worked perfectly and produced the desired result, but I’m always uneasy when Something Seems Wrong.

    Messing around with the code produced a slightly different support structure:

    Glass Tile Frame - quad support
    Glass Tile Frame – quad support

    The one thread thick square on the bottom helps glue the structure to the platform and four ribs work just as well as eight in the octagonal hole:

      Fin = [Screw[OD]/2 - 1.5*ThreadWidth,2*ThreadWidth,ScrewRecess - ThreadThick];
      if (Inserts && SupportInserts)
        color("Yellow")
          for (i=[-1,1], j=[-1,1])
            translate([i*InsertOC.x/2,j*InsertOC.y/2,0]) {
              rotate(180/8)
                cylinder(d=6*ThreadWidth,h=ThreadThick,$fn=8);
              for (a=[0:90:360])
                  rotate(a)
                    translate([Fin.x/2 + ThreadWidth/2,0,(ScrewRecess - ThreadThick)/2])
                      cube(Fin,center=true);
            }

    Which changed the NaN time estimates into actual numbers.

    One key difference may be the small hole in the middle. The four ribs (not two!) now overlap by one thread width around the hole, so they’re not quite coincident and Slic3r produces a tidy model:

    Glass Tile Frame - quad support - Slic3r
    Glass Tile Frame – quad support – Slic3r

    The hole eliminates a smear of infill from the center, which may have something to do with the improvement.

    In any event, I have an improved copypasta recipe for the next screw holes in need of support, even if I don’t understand why it’s better.

  • Glass Tiles: Matrix for SK6812 PCBs

    Glass Tiles: Matrix for SK6812 PCBs

    Tweaking the glass tile frame for press-fit SK6812 PCBs in the bottom of the array cells:

    Glass Tile Frame - cell array - openscad
    Glass Tile Frame – cell array – openscad

    Which looks like this with the LEDs and brass inserts installed:

    Glass Tile - 2x2 array - interior
    Glass Tile – 2×2 array – interior

    The base holds an Arduino Nano with room for wiring under the cell array:

    Glass Tile Frame - base - openscad
    Glass Tile Frame – base – openscad

    Which looks like this after it’s all wired up:

    Glass Tile - 2x2 array - wiring
    Glass Tile – 2×2 array – wiring

    The weird colors showing through the inserts are from the LEDs. The red thing in the upper left is a silicone insulation snippet. Yes, that’s hot-melt glue holding the Arduino Nano in place and preventing the PCBs from getting frisky.

    Soak a handful of glass tiles overnight in paint stripper:

    Glass Tiles - paint stripper soak
    Glass Tiles – paint stripper soak

    Whereupon the adhesive slides right off with the gentle application of a razor scraper. Rinse carefully, dry thoroughly, and snap into place.

    Tighten the four M3 SHCS and it’s all good:

    Glass Tile - 2x2 array - operating
    Glass Tile – 2×2 array – operating

    So far, I’ve had two people tell me they don’t know what it is, but they want one:

    Glass Tile - various versions
    Glass Tile – various versions

    The OpenSCAD Customizer lets you set the array size:

    Glass Tile Frame - 3x3 - press-fit SK6812 LEDs
    Glass Tile Frame – 3×3 – press-fit SK6812 LEDs

    However, just because you can do something doesn’t mean you should:

    Glass Tile Frame - 6x6 cell array - openscad
    Glass Tile Frame – 6×6 cell array – openscad

    Something like this might be interesting:

    Glass Tile Frame - 2x6 cell array - openscad
    Glass Tile Frame – 2×6 cell array – openscad

    In round numbers, printing the frame takes about an hour per cell, so a 2×2 array takes three hours and 3×3 array runs around seven hours. A 6×6 frame is just not happening.

    The OpenSCAD source code as a GitHub Gist:

    // Illuminated Tile Grid
    // Ed Nisley – KE4ZNU
    // 2020-05
    /* [Configuration] */
    Layout = "Build"; // [Cell,CellArray,MCU,Base,Show,Build]
    Shape = "Square"; // [Square, Pyramid, Cone]
    Cells = [2,2];
    CellDepth = 15.0;
    Inserts = true;
    SupportInserts = true;
    /* [Hidden] */
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    Protrusion = 0.1; // make holes end cleanly
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Tile = [25.0 + 0.1,25.0 + 0.1,4.0];
    WallThick = 4*ThreadWidth;
    FloorThick = 3.0;
    Flange = [2*ThreadWidth,2*ThreadWidth,0]; // ridge supporting tile
    Separator = [3*ThreadWidth,3*ThreadWidth,Tile.z – 1]; // between tiles
    Screw = [3.0,6.0,3.5]; // M3 SHCS, OD=head, LENGTH=head
    Insert = [3.0,4.2,8.0]; // threaded brass insert
    ScrewRecess = Screw[LENGTH] + 4*ThreadThick;
    LEDPCB = [9.6,9.6,2.9]; // round SK6812, squared-off sides
    LED = [5.0 + 2*HoleWindage,5.0 + 2*HoleWindage,1.3];
    LEDOffset = [0.0,0.0,0.0]; // if offset from PCB center
    CellOAL = [Tile.x,Tile.y,0] + Separator + [0,0,CellDepth] + [0,0,FloorThick];
    ArrayOAL = [Cells.x*CellOAL.x,Cells.y*CellOAL.y,CellOAL.z]; // just the LED cells
    BlockOAL = ArrayOAL + [2*WallThick,2*WallThick,0]; // LED cells + exterior wall
    echo(str("Block OAL: ",BlockOAL));
    InsertOC = ArrayOAL – [Insert[OD],Insert[OD],0] – [WallThick,WallThick,0];
    echo(str("Insert OC: ",InsertOC));
    TapeThick = 1.0;
    Arduino = [44.0,18.0,8.0 + TapeThick]; // Arduino Nano to top of USB Mini-B plug
    USBPlug = [15.0,11.0,9.0]; // USB Mini-B plug insulator
    USBOffset = [0,0,5.0]; // offset from PCB base
    WiringSpace = 3.5;
    WiringBay = [(Cells.x – 1)*CellOAL.x + LEDPCB.x,(Cells.y – 1)*CellOAL.y + LEDPCB.x,WiringSpace];
    PlateOAL = [BlockOAL.x,BlockOAL.y,FloorThick + Arduino.z + WiringSpace]; // allow wiring above Arduino
    echo(str("Base Plate: ",PlateOAL));
    echo(str("Screw length: ",(PlateOAL.z – ScrewRecess) + Insert.z/2," to ",(PlateOAL.z – ScrewRecess) + Insert.z));
    LegendRecess = 1*ThreadThick;
    //————————
    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);
    }
    //———————–
    // Base and optics in single tile
    module LEDCone() {
    hull() {
    translate([0,0,CellDepth + Tile.z/2])
    cube(Tile – 2*[Flange.x,Flange.y,0],center=true);
    if (Shape == "Square") {
    translate([0,0,LEDPCB.z/2])
    cube([Tile.x,Tile.y,LEDPCB.z] – 2*[Flange.x,Flange.y,0],center=true);
    }
    else if (Shape == "Pyramid") {
    translate([0,0,LEDPCB.z/2])
    cube(LEDPCB,center=true);
    }
    else if (Shape == "Cone") {
    translate([0,0,LEDPCB.z/2])
    cylinder(d=1.0*LEDPCB.x,h=LED.z,center=true);
    }
    else {
    echo(str("Whoopsie! Invalid Shape: ",Shape));
    cube(5);
    }
    }
    }
    // One complete LED cell
    module LEDCell() {
    difference() {
    translate([0,0,CellOAL.z/2])
    cube(CellOAL + [Protrusion,Protrusion,0],center=true); // force overlapping adjacent sides!
    translate([0,0,CellOAL.z – Separator.z + Tile.z/2])
    cube(Tile,center=true);
    translate([0,0,LEDPCB.z])
    LEDCone();
    // cube([LED.x,LED.y,CellOAL.z],center=true);
    translate(-LEDOffset + [0,0,-CellOAL.z/2])
    rotate(180/8)
    PolyCyl(LEDPCB.x,CellOAL.z,8);
    }
    }
    // The whole array of cells
    module CellArray() {
    difference() {
    union() {
    translate([CellOAL.x/2 – Cells.x*CellOAL.x/2,CellOAL.y/2 – Cells.y*CellOAL.y/2,0])
    for (i=[0:Cells.x – 1], j=[0:Cells.y – 1])
    translate([i*CellOAL.x,j*CellOAL.y,0])
    LEDCell();
    if (Inserts) // bosses
    for (i=[-1,1], j=[-1,1])
    translate([i*InsertOC.x/2,j*InsertOC.y/2,0])
    rotate(180/8)
    cylinder(d=Insert[OD] + 2*WallThick,h=Insert[LENGTH],$fn=8);
    }
    if (Inserts) // holes
    for (i=[-1,1], j=[-1,1])
    translate([i*InsertOC.x/2,j*InsertOC.y/2,-Protrusion])
    rotate(180/8)
    PolyCyl(Insert[OD],Insert[LENGTH] + FloorThick + Protrusion,8);
    }
    difference() {
    translate([0,0,CellOAL.z/2])
    cube(BlockOAL,center=true);
    translate([0,0,CellOAL.z])
    cube(ArrayOAL + [0,0,2*CellOAL.z],center=true);
    }
    }
    // Arduino bounding box
    // Origin at center bottom of PCB
    module Controller() {
    union() {
    translate([0,0,Arduino.z/2])
    cube(Arduino,center=true);
    translate([Arduino.x/2 – Protrusion,-USBPlug.y/2,USBOffset.z + TapeThick – USBPlug.z/2])
    cube(USBPlug + [Protrusion,0,0],center=false);
    }
    }
    // Baseplate
    module BasePlate() {
    difference() {
    translate([0,0,PlateOAL.z/2])
    cube(PlateOAL,center=true);
    translate([PlateOAL.x/2 – Arduino.x/2 – 2*WallThick,0,FloorThick])
    Controller();
    translate([PlateOAL.x/2 – Arduino.x/2 – 2*WallThick,0,FloorThick + PlateOAL.z/2])
    cube([Arduino.x – 2*2.0,WiringBay.y,PlateOAL.z],center=true); // cutouts beside MCU
    translate([0,0,PlateOAL.z – WiringBay.z + PlateOAL.z/2 – Protrusion])
    cube([PlateOAL.x – 2*WallThick,WiringBay.y,PlateOAL.z],center=true); // cutout above MCU
    translate([0,0,PlateOAL.z – WiringBay.z + PlateOAL.z/2 – Protrusion])
    cube([WiringBay.x,PlateOAL.y – 2*WallThick,PlateOAL.z],center=true); // cutout above MCU
    if (Inserts)
    for (i=[-1,1], j=[-1,1])
    translate([i*InsertOC.x/2,j*InsertOC.y/2,-Protrusion])
    rotate(180/8) {
    PolyCyl(Screw[ID],2*PlateOAL.z,8);
    PolyCyl(Screw[OD],ScrewRecess + Protrusion,8);
    }
    cube([45,17.0,2*LegendRecess],center=true);
    }
    linear_extrude(height=2*LegendRecess) {
    translate([0,1])
    rotate(-0*90) mirror([1,0,0])
    text(text="Ed Nisley",size=6,font="Arial:style:Bold",halign="center");
    translate([0,-6.5])
    rotate(-0*90) mirror([1,0,0])
    text(text="softsolder.com",size=4.5,font="Arial:style:Bold",halign="center");
    }
    Fin = [Screw[OD]/2 – 1.5*ThreadWidth,2*ThreadWidth,ScrewRecess – ThreadThick];
    if (Inserts && SupportInserts)
    color("Yellow")
    for (i=[-1,1], j=[-1,1])
    translate([i*InsertOC.x/2,j*InsertOC.y/2,0]) {
    rotate(180/8)
    cylinder(d=6*ThreadWidth,h=ThreadThick,$fn=8);
    for (a=[0:90:360])
    rotate(a)
    translate([Fin.x/2 + ThreadWidth/2,0,(ScrewRecess – ThreadThick)/2])
    cube(Fin,center=true);
    }
    }
    //———————–
    // Build things
    if (Layout == "Cell")
    LEDCell();
    else if (Layout == "CellArray")
    CellArray();
    else if (Layout == "MCU")
    Controller();
    else if (Layout == "Base")
    BasePlate();
    else if (Layout == "Show") {
    translate([0,0,3*PlateOAL.z])
    CellArray();
    BasePlate();
    translate([PlateOAL.x/2 – Arduino.x/2 – 2*WallThick,0,FloorThick])
    color("Orange",0.3)
    Controller();
    }
    else if (Layout == "Build") union() {
    translate([0,0.6*BlockOAL.y,0])
    CellArray();
    translate([0,-0.6*BlockOAL.x,0])
    rotate(90)
    BasePlate();
    }