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

  • BOB Yak Trailer: Fender Front Mount

    BOB Yak Trailer: Fender Front Mount

    I eventually tracked a distressingly loud rattle from the BOB Yak trailer to a fender mount failure:

    BOB Yak Trailer fender front mount - aluminum fatigue
    BOB Yak Trailer fender front mount – aluminum fatigue

    The screw clamped the round aluminum fender between two flat washers (the other of which has been touring the workbench). The hole in the aluminum started as a screw slot and eventually fretted away around the edge of the washers, leaving a trapped fragment to fall out as I loosened the screw.

    Well, this mount lasted a decade longer than the wire mount at the top of the fender, so there’s that.

    As before, a bit of math conjures a chunky mount from the vasty digital deep:

    Fender front mount - solid model - Show view
    Fender front mount – solid model – Show view

    The first iteration didn’t have the hole for the threaded insert angled downward at 10°, but it’s easier to make better measurements with a “pretty close” prototype. I’m reasonably sure the angle is a glitch due to hand-brazing the frame tubes, but we’ll never know.

    The inner plate angles to match the insert, thus keeping the screw & washer perpendicular to the surface:

    Fender front mount - solid model - Mounts view
    Fender front mount – solid model – Mounts view

    A brim around that chip of plastic ensures a good grip on the platform:

    BOB Yak Trailer - fender front mount - PrusaSlicer preview
    BOB Yak Trailer – fender front mount – PrusaSlicer preview

    I suppose rounding the corners would make it prettier:

    BOB Yak Trailer fender front mount - inner plate
    BOB Yak Trailer fender front mount – inner plate

    The original screw was slightly too short, so that’s a shiny replacement from the Drawer o’ Random M5 Screws. If I ever have occasion to go in there again, I’ll use a button head screw, although there’s certainly enough clearance:

    BOB Yak Trailer fender front mount - tire clearance
    BOB Yak Trailer fender front mount – tire clearance

    From the top, the gray PETG-CF looks like it grew there:

    BOB Yak Trailer fender front mount - installed
    BOB Yak Trailer fender front mount – installed

    I figured the mount’s radius by feeding measurements into the chord equation and assuming the overall curve is circular; the radius came out slightly too large, which likely won’t make much difference.

    The OpenSCAD source code as a GitHub Gist:

    // BOB Yak Trailer – fender front mount
    // Ed Nisley – KE4ZNU
    // 2026-06-15
    include <BOSL2/std.scad>
    Layout = "Show"; // [Build,Show,Frame,Fender,OuterMount,InnerMount,Mounts]
    /* [Hidden] */
    ID = 0;
    OD = 1;
    LENGTH = 2;
    HoleWindage = 0.2;
    Protrusion = 0.01;
    NumSides = 4*3*2*4;
    Gap = 5.0/2;
    $fn=NumSides;
    WallThick = 5.0;
    Washer = [6.0,16.0,1.5]; // M5 fender washer
    Rivnut = [5.0,10.3,1.5]; // M5 rivnut in frame
    FrameOD = 16.1; // trailer frame tubing
    FrameAngle = 10;
    FenderOA = [52,440,1.5]; // minor major thickness
    BlockOA = [0,40.0,1.25*Washer[OD]];
    //—–
    // Define things
    // Relevant part of the trailer frame
    // origin at center of rivnut
    module Frame() {
    yrot(FrameAngle)
    union() {
    left(Rivnut[LENGTH])
    ycyl(2*BlockOA.y,d=FrameOD,anchor=RIGHT);
    xcyl(FrameOD/2,d=Rivnut[OD],anchor=RIGHT);
    left(Protrusion)
    xcyl(10,d=Rivnut[OD],anchor=LEFT);
    }
    }
    module OuterFender() {
    torus(od=FenderOA[OD],d_min=FenderOA[ID],orient=FRONT,anchor=LEFT);
    }
    module FullFender() {
    difference() {
    OuterFender();
    right(FenderOA[LENGTH]) // make it a cup
    torus(od=FenderOA[OD] – 2*FenderOA[LENGTH],d_min=FenderOA[ID] – 2*FenderOA[LENGTH],
    orient=FRONT,anchor=LEFT);
    right(FenderOA[ID]/2) // remove inner half
    ycyl(2*BlockOA.y,d=FenderOA[OD] – FenderOA[ID],anchor=LEFT);
    down(2*Washer[OD]) // remove bottom part
    cuboid(2*[FenderOA[OD],FenderOA[OD],FenderOA[OD]],anchor=TOP+LEFT);
    }
    }
    module OuterMount() {
    difference() {
    right(FenderOA[ID]/2)
    cuboid([FrameOD/3 + Washer[LENGTH] + FenderOA[ID]/2,BlockOA.y,BlockOA.z],
    rounding=1.0,anchor=RIGHT);
    Frame();
    OuterFender();
    }
    }
    module InnerMount() {
    difference() {
    render()
    intersection() {
    yrot(FrameAngle)
    cuboid([WallThick + FenderOA[LENGTH],BlockOA.y,BlockOA.z],anchor=LEFT);
    right(FenderOA[LENGTH])
    OuterFender();
    }
    yrot(FrameAngle)
    xcyl(FenderOA[ID],d=Washer[ID],anchor=LEFT);
    }
    }
    //—–
    // Build it
    if (Layout == "Frame") {
    Frame();
    }
    if (Layout == "Fender") {
    FullFender();
    }
    if (Layout == "OuterMount") {
    OuterMount();
    }
    if (Layout == "InnerMount") {
    InnerMount();
    }
    if (Layout == "Mounts") {
    OuterMount();
    InnerMount();
    }
    if (Layout == "Show") {
    OuterMount();
    InnerMount();
    color("Gray",0.6) {
    Frame();
    FullFender();
    }
    }
    if (Layout == "Build") {
    up(BlockOA.z/2) left(FenderOA[ID]/4)
    OuterMount();
    up(BlockOA.z/2)
    xrot(180)
    yrot(-FrameAngle)
    InnerMount();
    }
  • Earplug Case

    Earplug Case

    A no-assembly-needed earplug case from Printables will be more easily found in Mary’s purse than the previous small bag:

    Earplug case
    Earplug case

    That’s the “grippy bits” version of the model, which really is easier to open than the straight-sided version.

    I printed a few more, loaded them with earplugs, and put them where they may come in handy. In retrospect, I should have used clear PETG to show off the retina-burn plugs.

    Living in the future is great!

  • Outlet Strip Bench Mount

    Outlet Strip Bench Mount

    A spate of tidying-up led to mounting an outlet strip along the back of a bench:

    Outlet Bench Mount - installed
    Outlet Bench Mount – installed

    Rather than drill holes into the top of the bench for those screws, they fit into M4 brass inserts heat-staked into the brackets:

    Outlet Bench Mount - show view
    Outlet Bench Mount – show view

    The holes for those inserts aren’t centered side-to-side on the brackets, because the screw holes aren’t centered on the bent-steel angles forming the outlet strip endplates.

    The bottom arm on the brackets probably isn’t necessary, but they kept the outlet strip from crawling away while I match-drilled two holes for the screws into the side of the benchtop.

    For obvious reasons, the brackets print on their sides:

    Outlet Bench Mount - build view
    Outlet Bench Mount – build view

    Another outlet strip from a different manufacturer is, of course, different, but changing three parameters in the OpenSCAD program summons a different bracket from the vasty digital deep:

    Outlet Bench Mount - different brand
    Outlet Bench Mount – different brand

    Parametric modeling and a 3D printer are exactly the right hammers for the job …

    The OpenSCAD source code as a GitHub Gist:

    // Shower soap dish
    // Ed Nisley – KE4ZNU
    // 2026-06-04
    include <BOSL2/std.scad>
    Layout = "Show"; // [Show,Build]
    /* [Hidden] */
    HoleWindage = 0.2;
    Protrusion = 0.1;
    NumSides = 3*3*4;
    Gap = 10.0/2;
    $fn=NumSides;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    BenchThick = 21.0; // workbench top
    ScrewOD = 4.0; // into edge of bench
    Insert = [4.0,5.5,10.0]; // robust M4 insert
    WallThick = 10.0;
    BaseThick = 10.0;
    OutletBase = [15.0,40.0];
    HoleOffset = 6.5; // from outside edge of bracket
    HoleOC = 24.0;
    MountOA = [OutletBase.x,OutletBase.y,BenchThick + Insert[LENGTH] + 1.0 + BaseThick];
    //———-
    // Build it
    module Mount() {
    difference() {
    cuboid(MountOA,rounding=1.0,anchor=BOTTOM + BACK);
    up(BaseThick)
    fwd(WallThick)
    cuboid([2*MountOA.x,MountOA.y,BenchThick],anchor=BOTTOM + BACK);
    up(BaseThick + BenchThick/2) back(Protrusion)
    ycyl(OutletBase.y,d=ScrewOD,circum=true,$fn=6,anchor=BACK);
    for (j=[-1,1])
    fwd(MountOA.y/2 + j*HoleOC/2)
    right(HoleOffset – MountOA.x/2)
    up(MountOA.z + Protrusion)
    cyl(Insert[LENGTH],d=Insert[OD],circum=true,$fn=6,anchor=TOP);
    }
    }
    //———-
    // Build it
    if (Layout == "Show") {
    left(Gap + MountOA.x/2)
    Mount();
    right(Gap + MountOA.x/2)
    xflip() Mount(); // mirror for the other end of the outlet strip
    }
    if (Layout == "Build") {
    left(MountOA.z/2)
    up(MountOA.x/2)
    yrot(90)
    Mount();
    fwd(1.5*MountOA.y)
    left(MountOA.z/2 – BenchThick/2 – Insert[LENGTH]/2)
    zrot(180)
    up(MountOA.x/2)
    yrot(-90)
    xflip() Mount(); // mirror for the other end of the outlet strip
    }
  • Prusa MK4: Mesh Bed Leveling Temperature vs. 0.8 mm Ruby Durozzle

    Prusa MK4: Mesh Bed Leveling Temperature vs. 0.8 mm Ruby Durozzle

    After going through the ritual required to install the 0.8 mm nozzle and preload the filament, the hot end looks like this before installing the silicone sock:

    Prusa MK4 hot end - 0.8 mm Durozzle ruby - front
    Prusa MK4 hot end – 0.8 mm Durozzle ruby – front

    The aluminum block doesn’t look nearly as awful as these pictures suggest; those plastic smears serve as reminders of a few previous printing mishaps.

    The nozzle is a 0.8 mm Durozzle with a ruby tip suitable for abrasive filaments like PETG-CF, although this is gooey squishy “natural” TPU:

    Prusa MK4 hot end - 0.8 mm Durozzle ruby - bottom
    Prusa MK4 hot end – 0.8 mm Durozzle ruby – bottom

    The first patio table foot test piece in TPU had terrible adhesion to the Textured Sheet, which I eventually tracked down to an excessively thick first layer. Given that the MK4 homes the axes and performs mesh bed leveling probes over the build area, this was difficult to believe, particularly because it had never been a problem with the Prusa 0.4 mm ObXidian hardened steel nozzle.

    More poking around showed some of the plastic drool left on the outside of the nozzle from the previous print session (as shown in the two pictures above) could remain hardened or at least “not squishy” despite the nozzle being heated before homing and mesh probing. Because probing depends on having the nozzle touch the platform, anything between the nozzle and the steel sheet will raise the Z=0 position and cause all the layers to be too high.

    As far as I can tell, ruby has a thermal coefficient around 40 W/m·K, roughly the same as steel. Both are considerably lower than the 200-ish W/m·K for the aluminum block surrounding the nozzle tube, suggesting most of whatever temperature gradient there may be occurs between the heater and the nozzle, not in the nozzle.

    While puzzling that out, I noticed the nozzle heated to only 160 °C prior to homing and probing, which seemed low for a filament calling for 230 to 250 °C during printing. Ordinary PETG heated to 170 °C, so something was different.

    More puzzling showed the Start G-Code section of the printer’s Custom G-Code sets the home / probe temperature, herein reformatted for readability:

    M140 S[first_layer_bed_temperature] ; set bed temp
    
    M104 T0 S{((filament_notes[0]=~/.*MBL160.*/) ? 160 : 
    (filament_notes[0]=~/.*HT_MBL10.*/) ? (first_layer_temperature[0] - 10) : 
    (filament_type[0] == "PC" or filament_type[0] == "PA") ? (first_layer_temperature[0] - 25) : 
    (filament_type[0] == "FLEX") ? 210 : 
    (filament_type[0]=~/.*PET.*/) ? 175 : 
    170)} ; set extruder temp for bed leveling
    
    M109 T0 R{((filament_notes[0]=~/.*MBL160.*/) ? 160 : 
    (filament_notes[0]=~/.*HT_MBL10.*/) ? (first_layer_temperature[0] - 10) : 
    (filament_type[0] == "PC" or filament_type[0] == "PA") ? (first_layer_temperature[0] - 25) : 
    (filament_type[0] == "FLEX") ? 210 : 
    (filament_type[0]=~/.*PET.*/) ? 175 : 
    170)} ; wait for temp
    
    

    The bursts of line noise after the M104 Set Extruder Temperature and M109 Set Extruder Temperature and Wait commands consist of nested ternary operators sifting placeholder variables defined in other parts of the slicer configuration.

    I had set up the eSun TPU 95A filament parameters based on Prusa’s TPU definition. I eventually discovered that definition includes the text MBL160 in its Notes section, which satisfies the regex in the first ternary operator:

    (filament_notes[0]=~/.*MBL160.*/) ? 160 : … snippage …
    

    Which then emitted the M104 T0 S160 and M109 R160 commands into the G-Code to set the temperature.

    After considerably more flailing around while figuring this out, I changed the filament Notes to read:

    HT_MBL10 -- force higher probe temperature for Durozzle ruby nozzle
    mbl160 -- disabled by lowercase
    

    Which then falls through to the regex in the second ternary operator:

    (filament_notes[0]=~/.*HT_MBL10.*/) ? (first_layer_temperature[0] - 10) : 
    

    Which sets the temperature to 10 °C below the first layer temperature, which I had set to 230 °C, so the probing now occurs at 220 °C.

    I am not making this up.

    Although that may be a bit too hot, the drool on the nozzle softens nicely and smashes flat during probing, thus solving the immediate problem and, without further ado, produced good round and square TPU feet.

  • Translucent vs. Transparent PETG Soap Dishes

    Translucent vs. Transparent PETG Soap Dishes

    In addition to printing bendy objects with TPU, the 0.8 mm nozzle 3D-prints PETG into thin walls with better transparency than the default 0.4 mm nozzle:

    Clear PETG - 0.4 vs 0.8 mm nozzle - side view
    Clear PETG – 0.4 vs 0.8 mm nozzle – side view

    The wall is now 1.0 mm thick, rather than 0.6 mm, and is much closer to being transparent. Those gray links from the RPi camera mount inside the dishes help show the difference.

    The 2.0 mm thick base plate is also more transparent, but mostly just reveals the 0.4 mm thick infill layers:

    Clear PETG - 0.4 vs 0.8 mm nozzle - top view
    Clear PETG – 0.4 vs 0.8 mm nozzle – top view

    More study is needed, even if we already have far more soap dishes than strictly necessary.

  • Prusa MK4: Nozzle Change Checklist

    Prusa MK4: Nozzle Change Checklist

    Both the round and square TPU patio table feet came from a 0.8 mm nozzle on the Prusa MK4, which produces results much faster than the venerable Makergear M2’s 0.35 mm nozzle. However, for unknown reasons a 0.8 mm nozzle is not compatible with the MMU3, so changing from and to the default 0.4 mm nozzle requires a somewhat complex ritual.

    For context, the MK4 extruder and hot end:

    Prusa MK4 - extruder overview
    Prusa MK4 – extruder overview

    Because the MK4 automatically unloads the filament from the extruder (with help from auto-retracting filament spools) when using the MMU, the hot end doesn’t have any filament in it. Disconnect the PTFE tube from the fitting atop the extruder, insert the end of the TPU filament from its Polydryer box, and …

    Change to 0.8 mm nozzle:

    • Remove silicone sock from hot end
    • Install fixture to hold the hot end in place
    • Loosen the two knobs clamping the nozzle
    • Loosen nozzle with 7 mm socket wrench
    • Unscrew & remove nozzle by hand
    • Install new nozzle by hand
    • Tighten nozzle with wrench
    • Tighten those two knobs
    • Remove fixture
    • Install silicone sock

    Change MK4 settings using the LCD panel:

    • SettingsMMU = Off
    • SettingsHardwarePrinthead = 0.8 mm

    Then, with the TPU filament poked into the top of the extruder:

    • FilamentLoad Filament =FLEX

    You’ll want to extrude a few lengths just to settle everything in place.

    Switching back to the 0.4 mm nozzle proceeds in the opposite direction, starting with:

    • FilamentUnload Filament

    Something of a nuisance, but not unbearable.

  • Square Patio Table Feet

    Square Patio Table Feet

    For a square patio table (with one missing foot), of course:

    Patio Table Feet - installed
    Patio Table Feet – installed

    These are chunky enough to demonstrate they’re made of clear-ish TPU, at least when backlit:

    Patio Table Feet - installed - backlit
    Patio Table Feet – installed – backlit

    The interior of the leg determines what fits into it:

    Patio Table Feet - leg interior
    Patio Table Feet – leg interior

    I pried out another foot, scanned it, and blew out the contrast:

    Patio Table Foot - scan
    Patio Table Foot – scan

    Importing that into LightBurn let me draw a rectangle matching the measured size, then node-edit the corners to approximate the shape:

    Patio Table Foot - LightBurn layout
    Patio Table Foot – LightBurn layout

    Export that shape as an SVG, import into OpenSCAD, and turn it into a solid model:

    Patio Table Foot - solid model - show view
    Patio Table Foot – solid model – show view

    That’s the Show view simulating the actual positions, which demonstrates why the pair of legs at each corner wear mirror-imaged feet. The Build view arranges the pair more sensibly for 3D printing:

    Patio Table Foot - solid model - build view
    Patio Table Foot – solid model – build view

    The protrusions and their bumps went through several iterations on the way to being functional, with the black TPU prototype on the left being entirely too bendy and the first clear version requiring utility knife editing to fit the end posts inside the leg:

    Patio Table Feet - prototypes
    Patio Table Feet – prototypes

    The original feet seem to be injection-molded ABS with a flat bottom intended to erode one corner against whatever the table stands on. However, the legs splay out at 5° from the vertical, which makes the flat bottom I used for the first few iterations obviously wrong:

    Patio Table Feet - flat foot
    Patio Table Feet – flat foot

    Somebody who can math harder than I would resolve the two angles and all the measurements into a single transformation matrix, but I rotated the foot separately around the X and Y axes, trigged the lowest corner to the proper height, then chopped off everything below Z=0. Works for me.

    The OpenSCAD source code as a GitHub Gist:

    // Patio Table Foot – rectangular legs
    // Ed Nisley – KE4ZNU
    // 2026-05-26
    include <BOSL2/std.scad>
    Layout = "Show"; // [Show,Build]
    /* [Hidden] */
    HoleWindage = 0.2;
    Protrusion = 0.01;
    NumSides = 4*3*2*4;
    Gap = 5.0/2;
    $fn=NumSides;
    PadOA = [50,23.5,4.5];
    LegAngles = [5,5];
    EndStrut = [2.5 + 2.5,13.3 – 1.0,23.0];
    SideStrut = [12.0,5.5 – 1.0,13.0];
    Clearance = 0.5;
    StrutsOC = [44.0 – EndStrut.x,18.0 – SideStrut.y];
    //—–
    // Define it
    module Foot(angles = LegAngles) {
    difference() {
    up((PadOA.x/2)*abs(sin(angles.x)) + (PadOA.y/2)*abs(sin(angles.y)))
    xrot(angles.x) yrot(angles.y)
    union() {
    down(3*PadOA.z)
    linear_extrude(4*PadOA.z)
    left(PadOA.x/2) fwd(PadOA.y/2)
    import("Patio Table Foot – pad outline.svg",center=true);
    up(PadOA.z)
    for (i = [-1,1])
    right(i*StrutsOC.x/2)
    cuboid(EndStrut,anchor=BOTTOM) position(TOP)
    down(EndStrut.y/2) left(i*Clearance)
    pie_slice(r=(PadOA.x – StrutsOC.x)/2,ang=180,l=EndStrut.y,anchor=CENTER,spin=-i*90,orient=FRONT);
    up(PadOA.z)
    for (j = [-1,1])
    fwd(j*StrutsOC.y/2)
    cuboid(SideStrut,anchor=BOTTOM) position(TOP)
    down(SideStrut.x/2) zrot(90) right(j*Clearance)
    pie_slice(r=(PadOA.y – StrutsOC.y)/2,ang=180,l=SideStrut.x,anchor=CENTER,spin=j*90,orient=FRONT);
    }
    cuboid(4*PadOA,anchor=TOP);
    }
    }
    //—–
    // Build it
    if (Layout == "Show") {
    back(PadOA.y/2 + Gap)
    Foot();
    left(0.8*PadOA.x) fwd(PadOA.y) zrot(-90)
    yflip() Foot();
    }
    if (Layout == "Build") {
    union() {
    fwd(PadOA.y/2 + Gap)
    Foot();
    back(PadOA.y/2 + Gap)
    yflip() Foot();
    }
    }