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

  • HQ Sixteen: Grip Angle Plug

    HQ Sixteen: Grip Angle Plug

    The grip plug is the upper part of the assembly changing the angle of the HQ Sixteen’s front grips:

    Handlebar Grip Mount - show view - solid model
    Handlebar Grip Mount – show view – solid model

    It sticks into the machine’s handlebar control base in place of the original aluminum tube grips:

    HQ Sixteen - handlebar ribbon cable entry
    HQ Sixteen – handlebar ribbon cable entry

    Only the two lower setscrews held the original grip in place, but I made the plug tall enough to engage all four, which meant it needed a side port to ease the ribbon cable through on its way to the PCB inside:

    Handlebar Grip Mount - plug cable port - solid model
    Handlebar Grip Mount – plug cable port – solid model

    The plastic tube is obviously thicker than the aluminum tube, with four dents capturing the 10-32 setscrews (using a 3/32 inch wrench) to align it within the hole; the bore juuust passes the original connector.

    The plug glues into the angle block, which means all the stress from the grips passes through a thin ring of plastic just above the joint. So I added five 1.2 mm OD hard steel wires about 20 mm long:

    HQ Sixteen - grip plug steel wires
    HQ Sixteen – grip plug steel wires

    Five wires, because four didn’t seem like quite enough and six seemed like crowding too much steel into too little plastic. The holes are offset to avoid the setscrew dents, with one lined up directly under the cable port.

    A pair of alignment marks help get the orientation right while gluing:

    HQ Sixteen - grip angle alignment marks
    HQ Sixteen – grip angle alignment marks

    The control base angles away from the grip, leaving a little more than half unsupported:

    HQ Sixteen - base angle vs grip block - rear
    HQ Sixteen – base angle vs grip block – rear

    Pondering that picture suggested adding those steel wires.

    The angle block prints with its upper surface against the MK4’s platform to get good dimensions inside the recess for the plug, so I can’t add a wedge to that surface, nor can it go on the plug. Maybe a separate wedge glued around the plug?

    However, the pin header for that cable sits directly inside the base and a transparent cover (not shown here) extends outward over the casting against the grip:

    HQ Sixteen - left LED PCB
    HQ Sixteen – left LED PCB

    So maybe it’s like that and that’s the way it is.

    The OpenSCAD source code as a GitHub Gist:

    // Handiquilter HQ Sixteen front handlebar grip angle mount
    // Ed Nisley – KE4ZNU
    // 2024-11-29
    include <BOSL2/std.scad>
    Layout = "Show"; // [Show,Build,Plug,Block,Covers,Cover]
    Material = "All"; // [All,Cover,Text]
    // Angle w.r.t. base
    GripAngle = 20; // [10:30]
    // Plug glued, not screwed
    PlugGlue = true;
    // Square nuts, not inserts
    SquareNuts = true;
    // Additional length of bottom
    AddLength = 0; // [0:20]
    // Separation in Show display
    Gap = 5; // [0:20]
    /* [Hidden] */
    HoleWindage = 0.1;
    Protrusion = 0.1;
    NumSides = 2*3*4;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Grip = [19.7,22.4,20.0]; // (7/8)*INCH = 22.2 mm + roughness, LENGTH=OEM insertion depth
    GripRadius = Grip[OD]/2; // used everywhere
    Plug = [15.0,Grip[OD],45.0]; // inserts into handlebar base
    PlugRim = [Plug[ID],25.0,10.0]; // … sits against handlebar base
    BaseScrewPositions = [[11.0,12.0],[27.0,29.0]]; // setscrew offsets from rim top: side,rear
    BaseCutout = [Plug[OD]/2,Plug[ID],10]; // cable cutout into base
    BaseCutoutOffset = 18.0; // … centerline position w.r.t. rim
    WallThick = 7.0; // should at least fit insert length
    SupportSag = 0.4; // vertical sag over support structure
    MidLength = AddLength + 3.0; // total length allowing for grip tube stop
    TopOD = PlugRim[OD] + 2*WallThick;
    BotOD = Grip[OD] + 2*WallThick;
    BaseScrew = [4.0,4.8 + HoleWindage,1.0]; // HQ 10-32 screws, LENGTH=capture dent
    Insert = [5.4,6.0,6.0]; // M4 inserts in plug rim
    //Insert = [4.0,5.0,5.0]; // M4 inserts in plug rim
    Screw = [3.5,4.0,1]; // M4 screws through angle block to inserts
    ScrewHeadOD = 7.4 + 0.4; // M4 BHCS head + comfort
    SquareNut = [4.0,7.0,3.0 + 0.4]; // M4 square nut LENGTH + inset allowance
    NutInset = GripRadius – sqrt(pow(GripRadius,2) – pow(SquareNut[OD],2)/4);
    PinOD = 1.2; // plug reinforcing pins
    NumPins = 5;
    CoverThick = [3.5,9.5]; // low and high sides of grip covers
    CoverAngle = atan((CoverThick[1] – CoverThick[0])/Plug[OD]);
    LogoText = ["Sew","Fine"];
    LogoFont = "Fira Sans Condensed:style=SemiBold";
    LogoSize = 7.5;
    LogoColor = "Red";
    LogoThick = 0.8;
    //———-
    // Simulator for aluminum plug replacing handlebar in base
    module BasePlug() {
    difference() {
    union() {
    tube(Plug[LENGTH],(Plug[OD] – HoleWindage)/2,Plug[ID]/2,anchor=DOWN);
    tube(PlugRim[LENGTH],PlugRim[OD]/2,PlugRim[ID]/2,anchor=DOWN);
    }
    up(BaseCutoutOffset + PlugRim[LENGTH])
    left(Plug[OD]/4)
    resize(BaseCutout)
    yrot(90) zrot(180/8)
    cylinder(d=1,h=1,$fn=8,center=true);
    up(PlugRim[LENGTH])
    right(PlugRim[OD]/2 – 1.0)
    cube([2.0,1.0,1.0],center=true);
    for (i = [0:NumPins – 1])
    zrot(i*360/NumPins + 180/NumPins)
    down(Protrusion)
    right((Plug[OD] + Plug[ID])/4)
    zrot(180/6)
    cylinder(d=PinOD,h=2*PlugRim[LENGTH],$fn=6);
    for (k = [0:1]) // recesses in plug to capture base setscrews
    for (a = [0:1])
    up(PlugRim[LENGTH] + BaseScrewPositions[k][a])
    zrot(a*90)
    right(Plug[OD]/2)
    yrot(90) zrot(180/8)
    cylinder(d=BaseScrew[OD],h=2*BaseScrew[LENGTH],$fn=8,center=true);
    if (!PlugGlue)
    for (a = [0:1]) // inserts for angle block screws
    up(PlugRim[LENGTH]/2)
    zrot(a*90)
    yrot(90) zrot(180/8)
    cylinder(d=Insert[OD],h=2*PlugRim[OD],$fn=8,center=true);
    }
    }
    //———-
    // Block fitting against handlebar base with handlebar angle
    module AngleBlock() {
    difference() {
    hull() {
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    cylinder(d=TopOD,h=PlugRim[LENGTH],$fn=NumSides);
    for (a = [1:2:GripAngle+1])
    up((TopOD/2)*sin(a-1))
    hull() {
    xrot(a)
    cylinder(d=TopOD,h=0.1,$fn=NumSides);
    xrot(a-1)
    cylinder(d=TopOD,h=0.1,$fn=NumSides);
    }
    down(Grip[LENGTH] + MidLength)
    cylinder(d=(Grip[OD] + 2*WallThick),h=0.1,$fn=NumSides);
    }
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    down(SupportSag)
    cylinder(d=(PlugRim[OD] + HoleWindage),
    h=PlugRim[LENGTH] + SupportSag + Protrusion,
    $fn=NumSides);
    up((TopOD/2)*sin(GripAngle))
    sphere(d=PlugRim[ID],$fn=NumSides);
    cylinder(d=PlugRim[ID],h=(TopOD/2)*sin(GripAngle),$fn=NumSides);
    down(MidLength + Protrusion)
    cylinder(d=(Grip[ID] – 2.0),h=(MidLength + 2*Protrusion),$fn=NumSides);
    down(Grip[LENGTH] + MidLength + Protrusion)
    cylinder(d=(Grip[OD] + HoleWindage),h=(Grip[LENGTH] + Protrusion),$fn=NumSides);
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    up(PlugRim[LENGTH])
    right(PlugRim[OD]/2 + 0.9)
    cube([2.0,1.0,1.0],center=true);
    if (!PlugGlue) {
    for (a = [0:1])
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    up(PlugRim[LENGTH]/2)
    zrot(a*90)
    yrot(90) zrot(180/8)
    cylinder(d=Screw[OD],h=3*PlugRim[OD],$fn=8,center=true);
    for (a = [0:3])
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    up(PlugRim[LENGTH]/2)
    zrot(a*90)
    right(TopOD/2 – 2.0)
    yrot(90) zrot(180/8)
    cylinder(d=ScrewHeadOD,h=TopOD,$fn=8,center=false);
    }
    if (SquareNuts) {
    for (a = [0:1])
    for (k = [1,3])
    down(k*Grip[LENGTH]/4 + MidLength)
    zrot(a*90)
    right(BotOD/2)
    yrot(90) zrot(180/8)
    cylinder(d=SquareNut[ID],h=BotOD,$fn=8,center=true);
    for (a = [0:1])
    for (k = [1,3])
    down(k*Grip[LENGTH]/4 + MidLength)
    zrot(a*90)
    right(GripRadius + SquareNut[LENGTH]/2 – NutInset/2)
    yrot(90)
    cube([SquareNut[OD],SquareNut[OD],SquareNut[LENGTH] + NutInset],center=true);
    }
    else {
    for (a = [0:1])
    for (k = [1,3])
    down(k*Grip[LENGTH]/4 + MidLength)
    zrot(a*90)
    right(BotOD/2)
    yrot(90) zrot(180/8)
    cylinder(d=Insert[OD],h=BotOD,$fn=8,center=true);
    }
    }
    }
    //———-
    // Chip fitting against handlebar base matching top angle
    // Text will be invisible until sliced
    module GripCover(loc=LEFT,matl="Cover") {
    if (matl == "Text" || matl == "All")
    color(LogoColor)
    down(matl == "All" ? 0.01 : 0.0)
    text3d(LogoText[loc == LEFT ? 0 : 1],LogoThick,LogoSize,LogoFont,
    orient=DOWN,anchor=TOP,atype="ycenter");
    if (matl == "Cover" || matl == "All")
    difference() {
    intersection() {
    yrot(loc == RIGHT ? -CoverAngle : CoverAngle)
    cylinder(d=Plug[OD],h=(CoverThick[0] + CoverThick[1]),anchor=CENTER);
    cube(2*Plug[OD],anchor=BOTTOM);
    }
    text3d(LogoText[loc == LEFT ? 0 : 1],LogoThick,LogoSize,LogoFont,
    orient=DOWN,anchor=TOP,atype="ycenter");
    }
    }
    //———-
    // Build things
    if (Layout == "Cover") {
    GripCover(LEFT,Material);
    }
    if (Layout == "Covers") {
    left(Plug[OD]) GripCover(LEFT,"Cover");
    left(Plug[OD]) GripCover(LEFT,"Text");
    right(Plug[OD]) GripCover(RIGHT,"Cover");
    right(Plug[OD]) GripCover(RIGHT,"Text");
    }
    if (Layout == "Plug")
    BasePlug();
    if (Layout == "Block")
    AngleBlock();
    if (Layout == "Show") {
    up((TopOD/2)*sin(GripAngle) + Protrusion)
    xrot(GripAngle)
    up(Plug[LENGTH] + CoverThick[1] + Gap)
    yrot(180 + CoverAngle)
    GripCover(RIGHT,"All");
    up((TopOD/2)*sin(GripAngle) + Protrusion)
    xrot(GripAngle)
    up(Gap)
    color("Lime",0.75)
    BasePlug();
    render()
    difference() {
    AngleBlock();
    back(50) right(50)
    cube(100,center=true);
    }
    color("Silver",0.5)
    down(MidLength + Gap)
    tube(3*Grip[LENGTH],GripRadius,Grip[ID]/2,anchor=TOP);
    }
    if (Layout == "Build") {
    mirror_copy([1,0,0]) {
    right(BotOD) {
    up((TopOD/2)*sin(GripAngle) + PlugRim[LENGTH]*cos(GripAngle) + Protrusion)
    xrot(180 – GripAngle)
    AngleBlock();
    back(1.5*max(TopOD,BotOD))
    BasePlug();
    }
    }
    fwd(60) {
    left(Plug[OD]) GripCover(LEFT,"Cover");
    right(Plug[OD]) GripCover(RIGHT,"Cover");
    }
    fwd(60) {
    left(Plug[OD]) GripCover(LEFT,"Text");
    right(Plug[OD]) GripCover(RIGHT,"Text");
    }
    }
  • Handi-Quilter HQ Sixteen: Handlebar Grip Angle Adjustment

    Handi-Quilter HQ Sixteen: Handlebar Grip Angle Adjustment

    With the handlebar assembly angled to let Mary see the LCD panel, the grips no longer meet her hands at the proper angle:

    HQ Sixteen - remounted handlebars in use
    HQ Sixteen – remounted handlebars in use

    Each grip has two buttons intended for thumb operation, but at that angle her thumbs lack oomph.

    So I added a compensating angle just under the handlebar control assembly:

    HQ Sixteen - grip angle blocks installed
    HQ Sixteen – grip angle blocks installed

    Restoring the front part to vertical means she can walk up to the machine, grab the grips at a neutral wrist angle, and start sewing.

    Which required several iterations:

    HQ Sixteen - grip angle block evolution
    HQ Sixteen – grip angle block evolution

    The pictures show various setups as we installed, tried, tweaked, and replaced nearly everything along that progression from left to right. They’re similar, but the details made all the difference.

    This is an overview of the adapter, with details to follow over the next few days.

    The solid model shows how the pieces go together:

    Handlebar Grip Mount - show view - solid model
    Handlebar Grip Mount – show view – solid model

    The white chip on top fills the space between the surface of the base and the top of the plug, with some lettering just for pretty.

    The greenish plug (not its real color!) sticks into the handlebar control base, where its dimples capture four setscrews. The original grips extended only halfway into the base, leaving the top pair of tapped setscrew holes empty:

    HQ Sixteen - unused grip setscrews
    HQ Sixteen – unused grip setscrews

    The ribbon cable carries signals from the pushbuttons into the base assembly:

    HQ Sixteen - handlebar ribbon cable entry
    HQ Sixteen – handlebar ribbon cable entry

    Looks like a guillotine to me, too, but the foam rubber cover prevents the grips from sliding any further into the base:

    HQ Sixteen - base angle vs original grip
    HQ Sixteen – base angle vs original grip

    Despite the metric socket head cap screws used elsewhere on the machine, those are 10-32 setscrews. Took me a while to figure that out, as 10-32 setscrews are visually indistinguishable from M5 setscrews, but neither screw will thread into the other’s nuts even though their wrenches are equally sloppy fits in the other screw.

    The angle adapter block has an intricate geometry:

    Handlebar Grip Mount - show detail - solid model
    Handlebar Grip Mount – show detail – solid model

    Because I don’t know the proper angle, the OpenSCAD model includes enough trig to adjust from 10° to 30°, with the default at 20° to set the front of the grips vertical. The lower part of the block can extend to lower the grips if that turns out to be necessary, but we’ll start with zero millimeters.

    The grips slide into the bottom of the angle block where they’re captured by four M4 setscrews threaded through square nuts:

    HQ Sixteen - grip angle square nuts
    HQ Sixteen – grip angle square nuts

    The big washer over on the right sits under the screw I used to pull the nuts into their recesses, where they sit firmly without adhesive.

    The first iterations used heat-staked brass inserts that didn’t provide enough griptivity against the torque generated by shoving the grips sideways. I probably applied more force than they’ll ever see in real life, but I’m no Hulk and I didn’t like the feel.

    The upper plug gets glued into the lower angle with JB PlasticBonder urethane adhesive. Had they been finished, the first two iterations would have had screws through the angle block into brass inserts in the plug, but I realized adhesives would work much better. A pair of index marks aligns the two pieces:

    HQ Sixteen - grip angle alignment marks
    HQ Sixteen – grip angle alignment marks

    It’s early days, but the machine fits her much better than it did before.

    The OpenSCAD source code as a GitHub Gist:

    // Handiquilter HQ Sixteen front handlebar grip angle mount
    // Ed Nisley – KE4ZNU
    // 2024-11-29
    include <BOSL2/std.scad>
    Layout = "Show"; // [Show,Build,Plug,Block,Covers,Cover]
    Material = "All"; // [All,Cover,Text]
    // Angle w.r.t. base
    GripAngle = 20; // [10:30]
    // Plug glued, not screwed
    PlugGlue = true;
    // Square nuts, not inserts
    SquareNuts = true;
    // Additional length of bottom
    AddLength = 0; // [0:20]
    // Separation in Show display
    Gap = 5; // [0:20]
    /* [Hidden] */
    HoleWindage = 0.1;
    Protrusion = 0.1;
    NumSides = 2*3*4;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Grip = [19.7,22.4,20.0]; // (7/8)*INCH = 22.2 mm + roughness, LENGTH=OEM insertion depth
    GripRadius = Grip[OD]/2; // used everywhere
    Plug = [15.0,Grip[OD],45.0]; // inserts into handlebar base
    PlugRim = [Plug[ID],25.0,10.0]; // … sits against handlebar base
    BaseScrewPositions = [[11.0,12.0],[27.0,29.0]]; // setscrew offsets from rim top: side,rear
    BaseCutout = [Plug[OD]/2,Plug[ID],10]; // cable cutout into base
    BaseCutoutOffset = 18.0; // … centerline position w.r.t. rim
    WallThick = 7.0; // should at least fit insert length
    SupportSag = 0.4; // vertical sag over support structure
    MidLength = AddLength + 3.0; // total length allowing for grip tube stop
    TopOD = PlugRim[OD] + 2*WallThick;
    BotOD = Grip[OD] + 2*WallThick;
    BaseScrew = [4.0,4.8 + HoleWindage,1.0]; // HQ 10-32 screws, LENGTH=capture dent
    Insert = [5.4,6.0,6.0]; // M4 inserts in plug rim
    //Insert = [4.0,5.0,5.0]; // M4 inserts in plug rim
    Screw = [3.5,4.0,1]; // M4 screws through angle block to inserts
    ScrewHeadOD = 7.4 + 0.4; // M4 BHCS head + comfort
    SquareNut = [4.0,7.0,3.0 + 0.4]; // M4 square nut LENGTH + inset allowance
    NutInset = GripRadius – sqrt(pow(GripRadius,2) – pow(SquareNut[OD],2)/4);
    PinOD = 1.2; // plug reinforcing pins
    NumPins = 5;
    CoverThick = [3.5,9.5]; // low and high sides of grip covers
    CoverAngle = atan((CoverThick[1] – CoverThick[0])/Plug[OD]);
    LogoText = ["Sew","Fine"];
    LogoFont = "Fira Sans Condensed:style=SemiBold";
    LogoSize = 7.5;
    LogoColor = "Red";
    LogoThick = 0.8;
    //———-
    // Simulator for aluminum plug replacing handlebar in base
    module BasePlug() {
    difference() {
    union() {
    tube(Plug[LENGTH],(Plug[OD] – HoleWindage)/2,Plug[ID]/2,anchor=DOWN);
    tube(PlugRim[LENGTH],PlugRim[OD]/2,PlugRim[ID]/2,anchor=DOWN);
    }
    up(BaseCutoutOffset + PlugRim[LENGTH])
    left(Plug[OD]/4)
    resize(BaseCutout)
    yrot(90) zrot(180/8)
    cylinder(d=1,h=1,$fn=8,center=true);
    up(PlugRim[LENGTH])
    right(PlugRim[OD]/2 – 1.0)
    cube([2.0,1.0,1.0],center=true);
    for (i = [0:NumPins – 1])
    zrot(i*360/NumPins + 180/NumPins)
    down(Protrusion)
    right((Plug[OD] + Plug[ID])/4)
    zrot(180/6)
    cylinder(d=PinOD,h=2*PlugRim[LENGTH],$fn=6);
    for (k = [0:1]) // recesses in plug to capture base setscrews
    for (a = [0:1])
    up(PlugRim[LENGTH] + BaseScrewPositions[k][a])
    zrot(a*90)
    right(Plug[OD]/2)
    yrot(90) zrot(180/8)
    cylinder(d=BaseScrew[OD],h=2*BaseScrew[LENGTH],$fn=8,center=true);
    if (!PlugGlue)
    for (a = [0:1]) // inserts for angle block screws
    up(PlugRim[LENGTH]/2)
    zrot(a*90)
    yrot(90) zrot(180/8)
    cylinder(d=Insert[OD],h=2*PlugRim[OD],$fn=8,center=true);
    }
    }
    //———-
    // Block fitting against handlebar base with handlebar angle
    module AngleBlock() {
    difference() {
    hull() {
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    cylinder(d=TopOD,h=PlugRim[LENGTH],$fn=NumSides);
    for (a = [1:2:GripAngle+1])
    up((TopOD/2)*sin(a-1))
    hull() {
    xrot(a)
    cylinder(d=TopOD,h=0.1,$fn=NumSides);
    xrot(a-1)
    cylinder(d=TopOD,h=0.1,$fn=NumSides);
    }
    down(Grip[LENGTH] + MidLength)
    cylinder(d=(Grip[OD] + 2*WallThick),h=0.1,$fn=NumSides);
    }
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    down(SupportSag)
    cylinder(d=(PlugRim[OD] + HoleWindage),
    h=PlugRim[LENGTH] + SupportSag + Protrusion,
    $fn=NumSides);
    up((TopOD/2)*sin(GripAngle))
    sphere(d=PlugRim[ID],$fn=NumSides);
    cylinder(d=PlugRim[ID],h=(TopOD/2)*sin(GripAngle),$fn=NumSides);
    down(MidLength + Protrusion)
    cylinder(d=(Grip[ID] – 2.0),h=(MidLength + 2*Protrusion),$fn=NumSides);
    down(Grip[LENGTH] + MidLength + Protrusion)
    cylinder(d=(Grip[OD] + HoleWindage),h=(Grip[LENGTH] + Protrusion),$fn=NumSides);
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    up(PlugRim[LENGTH])
    right(PlugRim[OD]/2 + 0.9)
    cube([2.0,1.0,1.0],center=true);
    if (!PlugGlue) {
    for (a = [0:1])
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    up(PlugRim[LENGTH]/2)
    zrot(a*90)
    yrot(90) zrot(180/8)
    cylinder(d=Screw[OD],h=3*PlugRim[OD],$fn=8,center=true);
    for (a = [0:3])
    up((TopOD/2)*sin(GripAngle))
    xrot(GripAngle)
    up(PlugRim[LENGTH]/2)
    zrot(a*90)
    right(TopOD/2 – 2.0)
    yrot(90) zrot(180/8)
    cylinder(d=ScrewHeadOD,h=TopOD,$fn=8,center=false);
    }
    if (SquareNuts) {
    for (a = [0:1])
    for (k = [1,3])
    down(k*Grip[LENGTH]/4 + MidLength)
    zrot(a*90)
    right(BotOD/2)
    yrot(90) zrot(180/8)
    cylinder(d=SquareNut[ID],h=BotOD,$fn=8,center=true);
    for (a = [0:1])
    for (k = [1,3])
    down(k*Grip[LENGTH]/4 + MidLength)
    zrot(a*90)
    right(GripRadius + SquareNut[LENGTH]/2 – NutInset/2)
    yrot(90)
    cube([SquareNut[OD],SquareNut[OD],SquareNut[LENGTH] + NutInset],center=true);
    }
    else {
    for (a = [0:1])
    for (k = [1,3])
    down(k*Grip[LENGTH]/4 + MidLength)
    zrot(a*90)
    right(BotOD/2)
    yrot(90) zrot(180/8)
    cylinder(d=Insert[OD],h=BotOD,$fn=8,center=true);
    }
    }
    }
    //———-
    // Chip fitting against handlebar base matching top angle
    // Text will be invisible until sliced
    module GripCover(loc=LEFT,matl="Cover") {
    if (matl == "Text" || matl == "All")
    color(LogoColor)
    down(matl == "All" ? 0.01 : 0.0)
    text3d(LogoText[loc == LEFT ? 0 : 1],LogoThick,LogoSize,LogoFont,
    orient=DOWN,anchor=TOP,atype="ycenter");
    if (matl == "Cover" || matl == "All")
    difference() {
    intersection() {
    yrot(loc == RIGHT ? -CoverAngle : CoverAngle)
    cylinder(d=Plug[OD],h=(CoverThick[0] + CoverThick[1]),anchor=CENTER);
    cube(2*Plug[OD],anchor=BOTTOM);
    }
    text3d(LogoText[loc == LEFT ? 0 : 1],LogoThick,LogoSize,LogoFont,
    orient=DOWN,anchor=TOP,atype="ycenter");
    }
    }
    //———-
    // Build things
    if (Layout == "Cover") {
    GripCover(LEFT,Material);
    }
    if (Layout == "Covers") {
    left(Plug[OD]) GripCover(LEFT,"Cover");
    left(Plug[OD]) GripCover(LEFT,"Text");
    right(Plug[OD]) GripCover(RIGHT,"Cover");
    right(Plug[OD]) GripCover(RIGHT,"Text");
    }
    if (Layout == "Plug")
    BasePlug();
    if (Layout == "Block")
    AngleBlock();
    if (Layout == "Show") {
    up((TopOD/2)*sin(GripAngle) + Protrusion)
    xrot(GripAngle)
    up(Plug[LENGTH] + CoverThick[1] + Gap)
    yrot(180 + CoverAngle)
    GripCover(RIGHT,"All");
    up((TopOD/2)*sin(GripAngle) + Protrusion)
    xrot(GripAngle)
    up(Gap)
    color("Lime",0.75)
    BasePlug();
    render()
    difference() {
    AngleBlock();
    back(50) right(50)
    cube(100,center=true);
    }
    color("Silver",0.5)
    down(MidLength + Gap)
    tube(3*Grip[LENGTH],GripRadius,Grip[ID]/2,anchor=TOP);
    }
    if (Layout == "Build") {
    mirror_copy([1,0,0]) {
    right(BotOD) {
    up((TopOD/2)*sin(GripAngle) + PlugRim[LENGTH]*cos(GripAngle) + Protrusion)
    xrot(180 – GripAngle)
    AngleBlock();
    back(1.5*max(TopOD,BotOD))
    BasePlug();
    }
    }
    fwd(60) {
    left(Plug[OD]) GripCover(LEFT,"Cover");
    right(Plug[OD]) GripCover(RIGHT,"Cover");
    }
    fwd(60) {
    left(Plug[OD]) GripCover(LEFT,"Text");
    right(Plug[OD]) GripCover(RIGHT,"Text");
    }
    }
  • Windows 11 for the Linux Guy: Remote Desktop Protocol

    Windows 11 for the Linux Guy: Remote Desktop Protocol

    With Windows 11 and LightBurn running on a little PC perched atop the laser in the basement:

    BeeLink Win 11 PC - overview
    BeeLink Win 11 PC – overview

    I wanted to work with that desktop from my Comfy Chair upstairs, because I’m unwilling to stand up a Windows box specifically for another LightBurn installation, along with a nightmare KVM switch tangle for all the displays / keyboards / trackballs I run with Linux.

    At the Win 11 PC, turn on Remote Desktop connections:

    Remote Desktop enable
    Remote Desktop enable

    The Administrator is automatically allowed access, but I also allowed access for my local User (who does not have a Microsoft account), which requires the Administrator’s password. You’ll want to store that in a password manager, because typing line noise gets tedious.

    Upstairs on the Comfy Chair at the Linux box, install Remmina from the repository, then tweak some preferences:

    Remmina prefs - General
    Remmina prefs – General

    This being a LAN connection, pick the highest quality scaling, although that shouldn’t matter with a fullscreen display. I added a screen resolution matching my desktop landscape monitor:

    Remmina config - screen resolutions
    Remmina config – screen resolutions

    Somewhat to my surprise, selecting an RDP screen resolution larger than the HDMI monitor on the Win 11 box worked perfectly.

    Because the remote display will fill the entire screen in fullscreen mode, set the toolbar to “Peeking” mode making it barely visible at the top of the screen:

    Remmina prefs - Appearance
    Remmina prefs – Appearance

    I have yet to (figure out how to) enable the hotkey turning fullscreen mode on and off, so if the toolbar isn’t readily available there is no way to get out of fullscreen mode.

    Set up the RDP connection to the Win 11 box, using either the static IP address or whatever name the router assigns:

    Remmina config - Basic
    Remmina config – Basic

    I set the Win 11 box for a static IP address, then told the router to assign that IP to the box if it ever woke up asking for an address through a DHCP request. The process differs depending on which router you have and may not be needed. I (try to) nail down all the IP addresses, so anything using DHCP will be obviously in need of attention.

    Select the highest quality compression:

    Remmina config - Advanced
    Remmina config – Advanced

    With all that set up, double-clicking the appropriate line should fire up an RDP connection, perhaps with a peephole view of the Win 11 desktop:

    Remmina - small RDP window
    Remmina – small RDP window

    Hit the Toggle Fullscreen icon (hollow square, fifth down) to embiggen it:

    Remmina - fullscreen RDP window
    Remmina – fullscreen RDP window

    Your Win 11 desktop will be different; that’s the Apollo 17 lunar module ascent stage.

    The thin line along the center top is the Remmina toolbar, peeking over the edge. Move the mouse cursor up there to roll it down into view:

    Remmina - fullscreen RDP window - detail
    Remmina – fullscreen RDP window – detail

    Because this is a fullscreen view, hitting the Toggle Fullscreen icon (highlighted blue) is the only way out. It required a disturbing number of iterations before realizing none of the hotkeys worked, then figuring out how to enable toolbar peeking.

    Moving the mouse pointer to the bottom of the screen rolls up the Win 11 Task Bar (which I always set to Hide mode to get it out of the way):

    Remmina - fullscreen - task bar
    Remmina – fullscreen – task bar

    I pinned the LightBurn icon to the task bar where it’s easy to hit, as that’s the whole point of the exercise.

    And then It Just Works™:

    Remmina - fullscreen LightBurn
    Remmina – fullscreen LightBurn

    Because this is Windows, one user can sign onto the box from either the local keyboard or the RDP connection, but not both.

    Being an Old School type of guy, I reflexively save my work before trotting either upstairs or downstairs and signing on wherever I end up, but it’s the same file in the same program on the same hardware.

    The performance over the LAN and through Remmina is good enough to make the fullscreen session feels exactly like running LightBurn locally. In truth, LightBurn is not a particularly resource-heavy program.

    Then I deleted both Linux installations from the LightBurn license portal …

  • Windows 11 for the Linux Guy: Hardware

    Windows 11 for the Linux Guy: Hardware

    Contrary to what I thought a while ago, the least awful way to continue running LightBurn after Linux support goes away is to stand up a dedicated Windows 11 box:

    BeeLink Win 11 PC - overview
    BeeLink Win 11 PC – overview

    Lest it become lost in the clutter:

    BeeLink Win 11 PC - detail
    BeeLink Win 11 PC – detail

    It’s a BeeLink Mini S12 (whatever that means) and squats near the low end of PC performance these days. I chose it based on reports from folks at Squidwrench having used similar units for various purposes without much pain, plus motivation from one of those weird Amazon “coupons” knocking the price down; it now sells for about that same price without the coupon.

    It’s advertised as coming with Windows 11, but my advisors recommended a clean installation to get rid of crapware and possible pre-installed malware. I decided to start with the as-delivered system, then use the same product key to blow away the default installation.

    The box / packaging did not include a Microsoft Windows Product Key and going through the first boot setup process produced this disconcerting result:

    Win 11 license key - not found
    Win 11 license key – not found

    More disconcerting: Windows Defender (Microsoft’s antivirus scanner / system integrity checker) was inactive and could not be installed from the MS “Store”. While not conclusive proof of pre-installed malware, the situation certainly seemed suspicious.

    The seller sent a key that seemed to be for Windows 10:

    BeeLink MS Product Key - Win 10 - redacted
    BeeLink MS Product Key – Win 10 – redacted

    Having been assured this would also validate a Win 11 installation, I did a clean installation using a USB flash drive produced by the MS installer, was never asked for a key, and eventually got to this point:

    Win 11 Pro Installed Key - requires Digital License - redacted
    Win 11 Pro Installed Key – requires Digital License – redacted

    Despite the missing OEM key and the footnote, everything seems just ducky:

    Win 11 Activation with Digital License
    Win 11 Activation with Digital License

    I assume a clean installation blows away any malware resident on the “hard drive” (an M.2 solid state drive, of course), including rootkits and boot sector malware. My threat model does not include malware in the BIOS / UEFI firmware, which may be overoptimistic.

    I declined all the optional MS products, refused various MS subscriptions, and generally tried to kill off a myriad invasive / advertising / “customized for you” features along the way. A casual search will produce many helpful guides for that process; I expect the details will change as MS continues to extract information from us. I set up a non-Administrator account for myself specifically to run LightBurn.

    With that accomplished, I gave it a static IP address, created network shares to various directories on the “file server” (an ancient off-lease Dell Optiplex) holding the files I previously used with Linux LightBurn, installed Window LightBurn, got its preferences sorted out / restored from backup, and things eventually worked pretty much as intended.

    Not shown in the picture:

    This setup is intended for layout tweaking and laser control, not for protracted design work while standing in what’s now a 57 °F = 14 °C basement.

    For what it’s worth, I must run the laser’s water chiller for half an hour to raise the cooling water to the normal 20 °C operating range; it has a water-cooled pump serving as a little heater.

    The next step involved enabling Remote Desktop Protocol access so I can access the Windows box from my Comfy Chair at my usual battle station upstairs. More on that tomorrow …

  • Vole Traps: End of Season

    Vole Traps: End of Season

    A highly effective way to bait a rat trap for garden voles:

    Rat trap - still baited
    Rat trap – still baited

    The trap is a Victor M205 (in a 12-pack as M326) with a big yellow plastic bait pedal. The bait is pieces of walnut, secured to the pedal with generous strands of hot melt glue. The trick involves mechanically capturing the walnut by slobbering glue over & around it, forcing the vole to pull & tug while gnawing the last bit of goodness.

    Which generally ends badly:

    Rat trap - gnawed bait
    Rat trap – gnawed bait

    I do not begrudge the critters a fancy last meal; it’s gotta be better than their usual diet of carrots / radishes / turnips.

    Voles have no qualms about eating the bait from a sprung trap with a dead compadre a few millimeters away:

    Rat trap - empty bait
    Rat trap – empty bait

    They will sometimes eat the walnuts and their dead compadre.

    The plastic pedals work much better than the old-style metal pedals at holding the steel arm wire. The wire slides freely on the plastic, in contrast to the previous high-friction metal-on-metal latch.

    Some of the traps were entirely too sensitive and required slightly bending the tip of the arm wire upward to increase the friction on the plastic plate. Always always always handle armed traps by the wooden edges beside the kill bar, so when it accidentally snaps your fingers are nowhere near the business end.

    After I figured out how to properly bait the traps and we set out half a dozen traps in the most attractive crops, Mary’s garden produced 54 dead voles over the course of 90 days, sometimes in groups of three or four at a time. While this did not prevent all the crop damage, it definitely reduced the problem.

    Next year we’ll start early and probably reach triple digits by midsummer.

    The same technique with Victor M035 mouse traps (in 12-packs as M035-12) is brutally effective on house mice.

  • Surprise Eggs: 3D Printer Tests

    Surprise Eggs: 3D Printer Tests

    These cute little toys serve as 3D printer torture tests:

    Surprise Eggs
    Surprise Eggs

    Obviously, each egg can hold only one of those toys, but I had to run them off in both retina-burn orange PETG and black PETG-CF for comparison.

    These Surprise Egg models came from Thingiverse, but they’re also available on Printables. You’ll find many more, of course, at a variety of scales, with these on the small end.

    The white eggs print with no difficulty at all, as does most of the equipment contained within:

    Surprise Eggs - contents - orange PETG
    Surprise Eggs – contents – orange PETG

    Most moving parts require careful back-and-forth movement to break them free, but they’re surprisingly functional. The PETG-CF, printed with an Extrusion Multiplier = 0.8, looks better, although the moving parts were more firmly stuck together.

    Not all of the equipment came out perfectly:

    Surprise Eggs - contents on platform
    Surprise Eggs – contents on platform

    Even without any special preparation, the MK4 didn’t have much trouble. If you were doing those for real, you could add stickum to the sheet or switch to a sheet with absurdly high PETG griptivity.

    My designs look downright chunky by comparison …

  • Handi-Quilter HQ Sixteen: Anchor Block

    Handi-Quilter HQ Sixteen: Anchor Block

    Although I devoted considerable attention to leveling & shimming the table under Mary’s HQ Sixteen, the machine rolls on ball bearing wheels atop (relatively) smooth plastic tracks. Parked at a few spots along the dozen feet of table, the machine will slowly and quietly roll away. This calls for some sort of parking brake, but until inspiration strikes, a simple anchor will suffice:

    HQ Sixteen - anchor
    HQ Sixteen – anchor

    It’s a cocoa container chosen from (one of) my Boxes o’ Containers, with a husky chunk of steel atop some very sticky double-sided foam tape inside the red lid.

    You can see one of the ball bearing wheel just above the strap applying tension to the practice quilt out of view on the left. The thing that looks like a wheel just under the strap is an encoder for the stitch regulator that we haven’t connected yet.

    To prevent the machine from simply bulldozing the container along with it, the lid sits on a sheet of EVA craft foam stuck to a sheet of rigid foam board (with adhesive on both sides).

    Scan the lid:

    Container lid scan
    Container lid scan

    Select all the red pixels, do a little cleanup, turn it into a binary mask:

    Container lid mask
    Container lid mask

    Import it into LightBurn, trace the perimeter, do some curve optimization / smoothing, duplicate the outline, set one to cut EVA foam and the other to cut adhesive board, and Fire The Laser.

    Elapsed time: about fifteen minutes from realizing what was needed to plunking the anchor in place.

    I briefly considered a full-frontal laser-cut finger-jointed box for the weight, but … Mary’s not a big fan of that campfire smell, particularly in a room dedicated to the Fiber Arts.