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: Software

General-purpose computers doing something specific

  • Desk Lamp Conversion: Photo Light Cold Shoe

    Having recently acquired a pair of photo lights and desirous of eliminating some desktop clutter, I decided this ancient incandescent (!) magnifying desk lamp had outlived its usefulness:

    Desk Lamp - original magnifiying head
    Desk Lamp – original magnifiying head

    The styrene plastic shell isn’t quite so yellowed in real life, but it’s close.

    Stripping off the frippery reveals the tilt stem on the arm:

    Desk Lamp - OEM mount arm
    Desk Lamp – OEM mount arm

    The photo lights have a tilt-pan mount intended for a camera’s cold (or hot) shoe, so I conjured an adapter from the vasty digital deep:

    Photo Light Bracket for Desk Lamp Arm - solid model
    Photo Light Bracket for Desk Lamp Arm – solid model

    Printing with a brim improved platform griptivity:

    Photo Light Bracket for Desk Lamp Arm - Slic3r preview
    Photo Light Bracket for Desk Lamp Arm – Slic3r preview

    Fortunately, the photo lights aren’t very heavy and shouldn’t apply too much stress to the layers across the joint between the stem and the cold shoe. Enlarging the stem perpendicular to the shoe probably didn’t make much difference, but it was easy enough.

    Of course, you (well, I) always forget a detail in the first solid model, so I had to mill recesses around the screw hole to clear the centering bosses in the metal arm plates:

    Photo Lamp - bracket recess milling
    Photo Lamp – bracket recess milling

    Which let it fit perfectly into the arm:

    Desk Lamp - photo lamp mount installed
    Desk Lamp – photo lamp mount installed

    The grody threads on the upper surface around the end of the slot came from poor bridging across a hexagon, so the new version has a simple and tity flat end. The slot is mostly invisible with the tilt-pan adapter in place, anyway.

    There being no need for a quick-disconnect fitting, a 1/4-20 button head screw locks the adapter in place:

    Photo Lamp - screw detail
    Photo Lamp – screw detail

    I stripped the line cord from inside the arm struts and zip-tied the photo lamp’s wall wart cable to the outside:

    Photo Lamp - installed
    Photo Lamp – installed

    And then It Just Works™:

    Photo Lamp - test image
    Photo Lamp – test image

    The lens and its retaining clips now live in the Big Box o’ Optical parts, where it may come in handy some day.

    The OpenSCAD source code as a GitHub Gist:

    // Photo light mount for desk lamp arm
    // Ed Nisley – KE4ZNU
    // 2019-03
    /* [Layout Options] */
    Layout = "Build"; // [Show,Build]
    Part = "Mount"; // [LampArm,ShoeSocket,Mount]
    /* [Extrusion Parameters] */
    ThreadWidth = 0.40;
    ThreadThick = 0.25;
    HoleWindage = 0.2;
    Protrusion = 0.1;
    //—–
    // Dimensions
    /* [Hidden] */
    ID = 0;
    OD = 1;
    LENGTH = 2;
    /* [Dimensions] */
    FrictionDisk = [4.0,16.5,11.0]; // squashed inside desk lamp arm frame
    Divots = [4.0,9.5,0.75]; // recesses for frame alignment bumps
    ArmLength = 30.0; // attached to disk
    ShoeWheelOD = 32.0; // lock wheel on photo lamp
    ShoeBase = [18.5,18.5,2.0] + [HoleWindage,HoleWindage,2*ThreadWidth]; // square base on photo lamp gimbal
    ShoeStem = [6.3,12.0,1.5]; // top slide clearance, ID = 1/4 inch screw
    ShoeBlock = [ShoeWheelOD,ShoeWheelOD,2*(ShoeBase.z + ShoeStem.z)]; // overall shoe block
    NumSides = 3*4;
    //—–
    // Useful routines
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    module PolyCyl(Dia,Height,ForceSides=0,Center=false) { // 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,center=Center);
    }
    //—–
    // Various Parts
    // Arm captured in disk lamp
    module LampArm() {
    difference() {
    union() {
    cylinder(d=FrictionDisk[OD],h=FrictionDisk[LENGTH],$fn=NumSides,center=true);
    hull()
    for (j=[-1,1])
    translate([0,j*(FrictionDisk[OD]/2 – FrictionDisk[LENGTH]/2),0]) {
    rotate([0,90,0]) rotate(180/NumSides)
    cylinder(d=FrictionDisk[LENGTH]/cos(180/NumSides),h=ArmLength/2,$fn=NumSides);
    translate([ArmLength – FrictionDisk[LENGTH]/2,0,0])
    sphere(d=FrictionDisk[LENGTH],$fn=NumSides);
    }
    }
    rotate(180/6) {
    PolyCyl(FrictionDisk[ID],FrictionDisk[LENGTH] + 2*Protrusion,6,Center=true);
    for (k=[-1,1])
    translate([0,0,k*(FrictionDisk[LENGTH]/2 – Divots[LENGTH]/2)])
    PolyCyl(Divots[OD],Divots[LENGTH] + Protrusion,6,Center=true);
    }
    }
    }
    // Basic hot shoe socket
    module ShoeSocket() {
    difference() {
    union() {
    cube(ShoeBlock,center=true); // overall blocky retainer
    translate([-ShoeBlock.x/2,0,0])
    cylinder(d=ShoeBlock.x,h=ShoeBlock.z,$fn=NumSides,center=true);
    }
    translate([0,0,-2*ShoeBlock.z]) // screw hole throughout
    rotate(180/6)
    PolyCyl(ShoeStem[ID],4*ShoeBlock.z,6);
    translate([0,0,ShoeBase.z/2]) // base slot under pillar
    cube([ShoeBase.x,ShoeBase.y,ShoeBase.z],center=true);
    translate([ShoeBase.x/2,0,ShoeBase.z/2]) // base slot opening
    cube([ShoeBase.x,ShoeBase.y,ShoeBase.z],center=true);
    translate([ShoeStem[OD]/2,0,ShoeBase.z/2 + ShoeStem[LENGTH]]) // stem slot
    cube([2*ShoeStem[OD],ShoeStem[OD],2*ShoeStem[LENGTH]],center=true);
    }
    }
    // Stick parts together
    module Mount() {
    rotate([90,0,0])
    LampArm();
    translate([ArmLength + ShoeBlock.x/2 – Protrusion,0,0])
    ShoeSocket();
    }
    //—–
    // Build things
    if (Layout == "Build") {
    rotate([0,90,0])
    translate([-(ArmLength + ShoeBlock.x),0,0])
    Mount();
    }
    if (Layout == "Show")
    if (Part == "LampArm")
    LampArm();
    else if (Part == "ShoeSocket")
    ShoeSocket();
    else if (Part == "Mount")
    Mount();

    The original dimension doodles, made before I removed the stem and discovered the recesses around the screw hole:

    Photo Light - Desk Lamp Arm Dimensions
    Photo Light – Desk Lamp Arm Dimensions
  • Poster Boilerplate: Whoopsie

    Spotted this in a lobby (clicky for more dots):

    Hannaford Reusable Bags - Poster Boilerplate
    Hannaford Reusable Bags – Poster Boilerplate

    I know no more than you do about the situation, but I’d lay long, long odds Hannaford created the poster with a more recent version of Microsoft Word (or whatever) than the recipient organization has available, making the file essentially read-only.

    Not casting shade on ’em; sometimes, you do what you gotta do.

    FWIW, I’d expect LibreOffice and any Microsoft Word version other than the exact one used to create the poster to mangle the formatting differently. Been there, done that.

  • Injured Arm Support Table: Wide Version

    This table must sit across the threshold of a walk-in / sit-down shower, with the shower curtain draped across the table to keep the water inside.

    Starting with another patio side table, as before, I installed a quartet of 5 mm stainless screws to lock the top panels in place and convert the table into a rigid assembly:

    Arm Supports - wide table - overview
    Arm Supports – wide table – overview

    Because the shower floor is slightly higher than the bathroom floor, I conjured a set of foot pads to raise the outside legs:

    Patio Side Table Feet - OpenSCAD model
    Patio Side Table Feet – OpenSCAD model

    The sloping top surface on the pads compensates for the angle on the end of the table legs:

    Arm Supports - leg end angle
    Arm Supports – leg end angle

    I think the leg mold produces legs for several different tables, with the end angle being Close Enough™ for most purposes. Most likely, it’d wear flat in a matter of days on an actual patio.

    Using good 3M outdoor-rated foam tape should eliminate the need for fiddly screw holes and more hardware:

    Arm Supports - leg pads
    Arm Supports – leg pads

    The feet fit reasonably well:

    Arm Supports - leg pad in place
    Arm Supports – leg pad in place

    They may need nonskid tape on those flat bottoms, but that’s in the nature of fine tuning.

    And, as with the narrow table, it may need foam blocks to raise the top surface to arm level. Perhaps a pair of Yoga Blocks will come in handy for large adjustments.

    The OpenSCAD source code as a GitHub Gist:

    // Patio Side Table Feet
    // Ed Nisley – KE4ZNU
    // 2019-03
    /* [Layout Options] */
    Layout = "Build"; // [Show,Build]
    /* [Extrusion Parameters] */
    ThreadWidth = 0.40;
    ThreadThick = 0.25;
    HoleWindage = 0.2;
    Protrusion = 0.1;
    //—–
    // Dimensions
    TapeThick = 1.0; // 3M double-stick outdoor tape
    LegWall = [2.5,3.5]; // leg walls are not the same in X and Y!
    LegBase = [36.0,19.0]; // flat on floor
    LegOuter = [31.0,19.0]; // perpendicular to leg axis
    LegInner = [28.5,11.5]; // … ditto
    LegAngle = 90 – 53; // vertical to leg
    LegRecess = [LegInner.x,LegInner.y,LegInner.x*tan(LegAngle)];
    PadWedge = 2; // to fit end of leg
    PadRadius = 4.0; // rounding radius for nice corners
    PadBase = [LegBase.x + 2*PadRadius,LegBase.y + 2*PadRadius,5.0];
    PadSides = 6*4;
    BathStep = 20; // offset between shower bottom and floor
    /* [Hidden] */
    EmbossDepth = 1*ThreadThick; // recess depth
    DebossHeight = 1*ThreadThick + Protrusion; // text height + Protrusion into part
    //—–
    // Useful routines
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    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);
    }
    //—–
    // Foot pad
    module FootPad(Riser = 0.0) {
    difference() {
    union() {
    hull()
    for (i=[-1,1], j=[-1,1]) {
    translate([i*(PadBase.x/2 – PadRadius),j*(PadBase.y/2 – PadRadius),0])
    cylinder(r=PadRadius,h=1,$fn=PadSides);
    translate([i*(PadBase.x/2 – PadRadius),
    j*(PadBase.y/2 – PadRadius),
    Riser + PadBase.z – PadRadius – (i-1)*PadWedge/2])
    sphere(r=PadRadius/cos(180/PadSides),$fn=PadSides);
    }
    translate([PadRadius – PadBase.x/2,0,Riser + PadBase.z])
    rotate([0,LegAngle,0])
    translate([LegRecess.x/2,0,(LegRecess.z – Protrusion)/2 ])
    cube(LegRecess – [2*TapeThick,0,2*TapeThick],center=true);
    }
    translate([0,0,-2*PadBase.z]) // remove anything under Z=0
    cube(4*PadBase,center=true);
    cube([17,12,2*DebossHeight],center=true);
    }
    mirror([1,0,0])
    linear_extrude(height=EmbossDepth)
    translate([0,0,0])
    text(text=str(Riser),size=10,spacing=1.05,
    font="Arial:style=Bold",
    valign="center",halign="center");
    }
    //—–
    // Build things
    if (Layout == "Build") {
    if (true) {
    translate([-0.7*PadBase.x,-0.7*PadBase.y,0])
    FootPad(0);
    translate([-0.7*PadBase.x,+0.7*PadBase.y,0])
    FootPad(0);
    }
    translate([+0.7*PadBase.x,-0.7*PadBase.y,0])
    FootPad(BathStep);
    translate([+0.7*PadBase.x,+0.7*PadBase.y,0])
    FootPad(BathStep);
    }
    if (Layout == "Show")
    FootPad();

  • 3D Printing: Native G-Code?

    From a discussion on the Makergear 3D printer forums

    From a new M2 user disillusioned by the learning curve:

    Is there a 3D CAD software out there that natively creates .g or .gcode files It’s not just a 3D printing thing.

    CAD (computer-aided design) software produces a solid model, which a CAM (computer-aided manufacturing) program then converts into the specific dialect(s) of G-Code required by whatever machine tool(s) will create the widget. You can create the solid model using many different CAD programs and convert it into G-Code with many different CAM programs, each with its own collection of features and warts.

    3D printing calls the CAM program a “slicer”, but it’s a different name for the process of converting geometry into machine instructions.

    Even in subtractive manufacturing using lathes and mills, you absolutely must understand how the G-Code interacts with the production hardware.

    I unfortunately don’t want to learn all the nuances and parameters of the slic3r software

    Then you must use a service like Shapeways: you create the model, send it to them, and get a neat widget a few days later. Their laser-sintered powder process provides much better built-in support than you’ll ever get from consumer-grade fused-filament printers, you can select from a wide variety of materials (including metals!), and, as long as you follow their straightforward design guidelines, you’ll never know how the magic happens.

    If you intend to create more than a trivial number of widgets, though, the cost in both cycle time and money will begin gnawing at you. In round numbers, I’ve been designing and printing one widget a week for the last seven years, so adding a printer to my basement shop and learning how to use it has been a major win.

  • Tour Easy: SRAM Grip Bushing

    After installing the X.0 shifter, I sprang for new grips:

    Tour Easy - SRAM X.0 grip shifter - new grip with bushing
    Tour Easy – SRAM X.0 grip shifter – new grip with bushing

    They’re 90 mm long, which turned out to be 4 mm shorter than the grips that came with the bike; a close look showed the original ones were cut down from SRAM’s 110 mm grips.

    Well, I can fix that:

    Tour Easy - SRAM grip bushings
    Tour Easy – SRAM grip bushings

    Ordinarily, you’d just move the brake levers by 4 mm and declare victory. In this case, moving the right lever would be easy, but the left one is firmly glued in place by the radio’s PTT button:

    PTT Button - rounded cap
    PTT Button – rounded cap

    Believe me, solid modeling is easy compared to redoing that!

    The OpenSCAD source code doesn’t amount to much:

    // SRAM grip shifter bushings
    // Ed Nisley KE4ZNU March 2019
    
    Protrusion = 0.1;           // make holes end cleanly
    
    //----------------------
    // Dimensions
    
    ID = 0;
    OD = 1;
    LENGTH = 2;
    
    Bushing = [22.2 + 0.5,31.0,4.0];        // ID = E-Z slip fit
    
    NumSides = 2*3*4;
    
    //----------------------
    // Build it!
    
    difference() {
      cylinder(d=Bushing[OD],h=Bushing[LENGTH],$fn=NumSides);
      translate([0,0,-Protrusion])
        cylinder(d=Bushing[ID],h=Bushing[LENGTH] + 2*Protrusion,$fn=NumSides);
    }

    I loves me my 3D printer …

  • 3D Printing: Peculiar Octopi Problem

    From a discussion on the Makergear 3D printer forums

    A Makergear M2 user had a strange problem:

    Octopi claims the serial connection went down.

    LED2 was blinking red, rapidly, and LED3 was shining with a steadfast red light.

    LED2 shows the extruder heater PID loop is running and LED3 shows the extruder fan is on:
    https://reprap.org/wiki/Rambo_v1.1

    You just never noticed the blinkiness before … [grin]

    Because the extruder heater is still running, the firmware hasn’t detected a (possibly bogus) thermal runaway or any other fatal problem. It’s just waiting for the next line of G-Code, but Octopi isn’t sending it.

    Casually searching the GitHub issues, there’s a report of intermittent serial problems from last year:
    https://github.com/foosel/OctoPrint/issues/2647

    Which points to the FAQ:
    https://community.octoprint.org/t/octop … eption/228

    Look at the Octopi Terminal log to see if the conversation just before the failure matches those descriptions.

    Assuming you haven’t updated the printer firmware or anything on the Octopi, then something physical has gone wrong.

    First and least obviously, the Pi’s MicroSD card has probably started to fail: they’re not particularly durable when used as a mass storage device and “the last couple of years” is more than you should expect. Download a fresh Octopi image, put it on a shiny-new, good-quality card (*), and see if the situation improves.

    Then I’d suspect the Pi’s power supply, even though you’re using the “official rpi power supply”. All of those things contain the cheapest possible electrolytic capacitors, running right on the edge of madness, and produce bizarre errors when they begin to go bad. Get a good-quality wall wart (**), ideally with a UL rating, and see if the situation improves.

    While you’re buying stuff, get a good-quality USB cable (***) to replace the one that (assuming you’re like me) you’ve been saving for the last decade Just In Case™. Use the shortest cable possible, because longer does not equal better.

    After that, the problems get truly weird. Apply some tweakage and report back.

    (*) This is harder to do than you might think. You may safely assume all cards available on eBay and all “Sold by X, Fulfilled by Amazon” cards will be counterfeit crap. I’ve been using Samsung EVO / EVO+ cards (direct from Samsung) with reasonable success:

    https://softsolder.com/2018/10/16/raspb … sk-memory/
    https://softsolder.com/2017/11/22/samsu … ification/
    https://www.samsung.com/us/computing/me … 22y+zq29p/

    The card in question eventually failed, so having a backup card ready to go was a Good Idea™.

    (**) Top-dollar may not bring top quality, but Canakit has a good rep and costs ten bucks through Prime.

    (***) Amazon Basics cables seems well-regarded and work well for what I’ve needed.

  • YAGV Hackage

    I’ve been using YAGV (Yet Another G-Code Viewer) as a quick command-line Guilloché visualizer, even though it’s really intended for 3D printing previews:

    YAGV previewer.png
    YAGV previewer.png

    Oddly (for a command-line program), it (seems to) lack any obvious keyboard shortcut to bail out; none of my usual finger macros work.

    A quick hack to the main /usr/share/yagv/yagv file makes Ctrl-Q bail out, thusly:

    diff yagv /usr/share/yagv/yagv 
    18a19
    > import sys
    364a366,367
    > 		if symbol==pyglet.window.key.Q and modifiers & pyglet.window.key.MOD_CTRL:
    > 			sys.exit()

    I tacked the code onto an existing issue, but yagv may be a defunct project. Tweaking the source works for me.

    The Ubuntu 18.04 LTS repo has what claims to be version 0.4, but the yagv GitHub repository (also claiming to be 0.4) includes code ignoring G-Code comments. Best to build the files from source (which, being Python, they already are), then add my Ctrl-Q hack, because my GCMC Guilloché generator adds plenty of comments.