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.

Tag: MK4

Prusa Mk 4 3D printer with MMU3 feeder

  • Laser-Engraved PETG / PETG-CF

    Laser-Engraved PETG / PETG-CF

    Prompted by scruss’s report of successfully “engraving” PLA, I had to try this:

    Laser engraved PETG-CF
    Laser engraved PETG-CF

    It’s blue PETG-CF from the scrap box, done at 500 mm/s and 20% of a 60 W laser and came out looking really nice.

    I did a pass at 10%, low enough that the laser barely fired, and the mark was, correspondingly, barely visible: no color change and only a slight depth. Obviously, you’d want to tune for best picture depending on whatever you were trying to achieve.

    The results on black PETG, also from the scrap box, were somewhat less attractive:

    Laser engraved PETG - bottom surface
    Laser engraved PETG – bottom surface

    That’s at 500 mm/s with power at 10% and 20, so the outcome definitely depends on the material. That surface was against the platform when it was printed on the Makergear M2, explaining the glossy smooth threads.

    The other side was rougher and needed more power to punch a visible result into the plastic:

    Laser engraved PETG - top surface
    Laser engraved PETG – top surface

    All in all, the PETG-CF result looks usable, particularly for small-ish annotations on a flat surface where full-on multimaterial printing would take forever without adding much value.

  • PrusaSlicer Scarf Joints

    PrusaSlicer Scarf Joints

    The release notes for PrusaSlicer 2.9 mention the addition of scarf joints on outer perimeters. Smooth joints seem like a Good Idea™, so I turned it on for comparison with a recent object:

    Double Gear fidget toy - scarf vs normal
    Double Gear fidget toy – scarf vs normal

    Those are flipped from the as-printed orientation: the orange ring builds upward, starting with two concentric threads on the platform.

    The normal aligned joint is on the right above, with a closer look here:

    Double Gear fidget toy - normal joInt
    Double Gear fidget toy – normal joInt

    The scarf joint has a offset between layers:

    Double Gear fidget toy - scarf joint
    Double Gear fidget toy – scarf joint

    The PrusaSlicer visualization shows the effect, looking up from below the platform:

    Double Gear fidget toy - scarf joint visualization
    Double Gear fidget toy – scarf joint visualization

    The blue PETG-CF parts have no visible seams anywhere with either setting, probably because the stuff swells slightly and obliterates any subtle differences.

    Scarf joints don’t make much difference for a fidget toy, but should improve the outcome for more critical circular / spherical models.

  • Plastic Plant Signs

    Plastic Plant Signs

    PrusaSlicer can recognize “things that look like logos” and process them with two different materials, so I tried it out with some plant signs:

    Plant Signs - 50pct scale
    Plant Signs – 50pct scale

    They came out surprisingly well, particularly for characters with two adjacent filament threads:

    Plant Signs - 50pct scale - 2-stroke
    Plant Signs – 50pct scale – 2-stroke

    Smaller characters with single threads show more stringing, a characteristic of PETG, but it brushes off easily enough:

    Plant Signs - 50pct scale - 1-stroke
    Plant Signs – 50pct scale – 1-stroke

    While the existing text isn’t nearly as informative as real plant tags, they’re surely more durable and a chunkier font would improve both printability and readability.

    I suggested Mary hand them out to any of her gardening cronies in need of a chuckle …

  • PrusaSlicer Flatpak: Adding an NFS Filesystem

    PrusaSlicer Flatpak: Adding an NFS Filesystem

    PrusaSlicer V 2.9.0 for Linux arrives as a Flatpak, instead of the previous AppImage, which wouldn’t matter except that the Flatpak sandbox prohibits access to anything outside each user’s home directory. I long ago set up access to the fileserver in the basement through filesystems mounted on /mnt, which is now inaccessible.

    Obviously, I’m not the first person to hit this issue, as some diligent searching turned up a hint leading to a description of Flatpak permissions, which eventually produced:

    sudo flatpak override com.prusa3d.PrusaSlicer --filesystem="/mnt/bulkdata"
    

    Overall, 2.9.0 seems significantly more sluggish and uglier than the 2.8.x series, but at least Prusa still supports Linux.

    Just to show PrusaSlicer can fetch files from the server and to have some pictures enhancing this post’s negligible SEO, I built a couple of Gear Fidget Toys:

    Double Gear fidget toy - on platform
    Double Gear fidget toy – on platform

    Which pop off the platform ready to roll:

    Double Gear fidget toy - finished
    Double Gear fidget toy – finished

    A trace of silicone grease eased between the pieces on a slip of paper makes the spinning action so smooth.

    As usual, the multi-material version takes twice as long to build due to all the filament swapping. I think I must improve the MMU3’s spoolholders, because the MMU3 (very) occasionally fails to ram the filament into the extruder, seemingly due to the force required to pull filament from the recalcitrant spools.

  • HQ Sixteen: Chin Light

    HQ Sixteen: Chin Light

    Setting the Handi-Quilter HQ Sixteen handlebars at a useful angle aimed the main PCB’s white LEDs at the front of the arm, rather than down at the needle:

    HQ Sixteen Chin Light - off
    HQ Sixteen Chin Light – off

    Having caused the problem, I must fix it:

    HQ Sixteen Chin Light - results
    HQ Sixteen Chin Light – results

    The light comes from a small chip-on-board LED affixed under the chin of the machine arm with heatsink tape:

    HQ Sixteen Chin Light - detail
    HQ Sixteen Chin Light – detail

    Yes, the pool of warm white COB LED light clashes horribly with the cool white 5 mm LEDs lighting the background (not to mention wintry daylight from the windows), but it’s sufficiently OK.

    I intended to run the wiring inside the machine arm, but all the pre-existing holes I wanted to use were oiling access points or blocked by whirling shafts inside, so the wire runs along the outside:

    HQ Sixteen Chin Light - wiring
    HQ Sixteen Chin Light – wiring

    The Handi-Quilter control & lighting goes through the bare gray ribbon cable to the handlebars, so I’m not too far down the stylin’ scale. The next version of the machine has round external cables, but this machine is what it is.

    I mounted the 12 VDC supply to the back panel of the machine’s power box with five 3 mm holes:

    HQ Sixteen Chin Light - power supply
    HQ Sixteen Chin Light – power supply

    A bag of right-angle barrel connectors will arrive shortly.

    The exposed wiring at the top (the white wires carry switched 120 VAC from the PCB inside the box) seemed … unaesthetic, so I conjured a cover from the vasty digital deep:

    Power Supply Cover - solid model
    Power Supply Cover – solid model

    Which fit neatly into place on the first try:

    HQ Sixteen Chin Light - supply cover fit test
    HQ Sixteen Chin Light – supply cover fit test

    That’s a trial fit, because I am not pulling the machine apart again until there’s more work to do inside.

    The blurry rocker switch below the Chin Light supply controls the machine power: turn it on and everything lights up as it should.

    The OpenSCAD source code as a GitHub Gist:

  • HQ Sixteen: Grip Angle Caps

    HQ Sixteen: Grip Angle Caps

    The stock Handi-Quilter HQ Sixteen has serviceable black rubber caps covering the holes for the grips:

    HQ Sixteen - original front handlebar mount
    HQ Sixteen – original front handlebar mount

    Because we live in the future, I can do better than that:

    HQ Sixteen - grip cap left
    HQ Sixteen – grip cap left

    In truth, the plastic grip plug now sticks up into the hole just beyond the top setscrews, leaving not enough room for the black plugs, so I had to make new covers.

    They’re a multi-material print using white PETG to kinda-sorta match the machine and blue PETG-CF to match the other plastic parts. The colors in the solid model just distinguish the two materials:

    Handlebar Grip Mount - plug caps - solid model
    Handlebar Grip Mount – plug caps – solid model

    The white covers have recesses exactly fitting the text:

    Handlebar Grip Mount - cap text recess - solid model
    Handlebar Grip Mount – cap text recess – solid model

    In some cases that’s not needed, but I’m unsure how PrusaSlicer knows what I intend and chopping the text out was easy, so that’s how I did it.

    I did not realize applying a transformation, like translate() or BOSL2’s syntactic sugar left(), to both the cover and the text implicitly joins them into a single “object”, so the slicer can’t distinguish them as separate materials. As a result, the OpenSCAD code must move the pieces separately:

    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");
    }
    

    Which is awkward, but not insurmountable.

    Export the model as a 3mf file, import it into PrusaSlicer, and you get separate objects for the cover and the text. Assign different materials and slice to produce a multi-material result, with the Wipe Tower in the background:

    Handlebar Grip Mount - cap first layer - PrusaSlicer
    Handlebar Grip Mount – cap first layer – PrusaSlicer

    They must print face down to merge the two colors into a single flat surface with a nubbly texture from the steel sheet’s coating.

    Disks of adhesive sheet will eventually stick them atop the plugs, but for now they’re just dropped into the holes.

    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");
    }
    }
  • HQ Sixteen: Grip Angle Block

    HQ Sixteen: Grip Angle Block

    The angle block joins the aluminum grip with the plug sticking into HQ Sixteen’s handlebar control base:

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

    Because I don’t know the exact angle until Mary puts more hours on the machine, the OpenSCAD code can tilt the plug from 10° to 30° with respect to the original grip. The bent part of the model consists of a succession of hulls around adjacent slices:

    Handlebar Grip Mount - bend slices - solid model
    Handlebar Grip Mount – bend slices – solid model

    An overall hull() then gloms everything into one solid lump, with all the negative features removed from it:

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

    After a brief flirtation with heat-staked brass inserts, four setscrews threaded into steel square nuts secure the original grip in the bottom:

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

    A screw behind that big washer pulled the nuts firmly into their sockets, where they stay without any adhesive. The square recesses include a little adder based on the curvature of the hole to sink the nuts deep enough:

    Handlebar Grip Mount - nut inset - solid model
    Handlebar Grip Mount – nut inset – solid model

    I made the block’s OD large enough to accommodate the brass inserts and hope it’s chunky enough to withstand the force from the setscrews. The inserts tended to creep outward after being snugged down, but the square nuts seem stable against the recesses.

    The block prints with the top surface against the platform to produce a clean recess for the plug, which requires support material for the ring around the bore. Because the ring sags slightly against the support, the model makes the recess 0.4 mm deeper, but the next iteration gets a little more:

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

    Not that it makes much difference.

    The bore from the grip meets the bore from the plug in a sphere centered at the bottom of the plug recess:

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

    A ball joint seems the best way to join a pair of intersecting cylinders, if you have room for the sphere, and eliminates a whole bunch of computations figuring the cylinder lengths; they just meet at about the center of the sphere and you’re done without anything sticking out. I’d like to pretend that was the first idea I had, but …

    The OpenSCAD code can add more length to the bottom of the block, in the event Mary wants the grips lower:

    Handlebar Grip Mount - added length - solid model
    Handlebar Grip Mount – added length – solid model

    That obviously increases the lever arm applied to the plug, but we’ll burn that bridge when we come to it.

    This lineup shows the progression from the first pass to something that might actually work:

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

    Rapid prototyping FTW!

    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");
    }
    }