Replacement Muntin Clips

Terminology I had to look up:

  • Window: something in a wall you can see through
  • Sash: a sliding panel in a window
  • Mullion: vertical post separating two windows
  • Muntin: strips separating glass panes in a sash

TIL: Muntin, which I’d always known was called a Mullion.

With that as preface, one of Mary’s quilting cronies lives in a very old house updated with vinyl windows sporting wood muntins arranged in a grille. The wood strips forming the grille end in plastic clips that snap into the sash, thereby holding the grill in place to make the window look more-or-less historically correct, while not being a dead loss as far as winter heating goes.

Time passed, sun-drenched plastic became brittle, and eventually enough clips broke that the grilles fell out. An afternoon quilting bee produced a question about the possibility of making a 3D printed clip, as the original manufacturer is either defunct or no longer offers that particular style of clip as a replacement part.

Well, I can do that:

Window Muntin Clips
Window Muntin Clips

The original is (obviously) the transparent injection-molded part in the upper left. The other two come hot off the M2’s platform, with the one on the right showing the support material under the sash pin.

The solid model looks about like you’d expect:

Window Muntin Clip - solid model
Window Muntin Clip – solid model

There is obviously no way to build it without support material, so I painted the bottom facet of the sash pin with a PrusaSlicer support enforcer:

Window Muntin Clip - PrusaSlicer
Window Muntin Clip – PrusaSlicer

The pin comes out slightly elongated top-to-bottom, but it’s still within the tolerances of the original part and ought to pop right into the sash. We’ll know how well it works shortly after the next quilting bee.

The doodle with useful measurements amid some ideas that did not work out:

Window Muntin Clip - Dimension Doodle
Window Muntin Clip – Dimension Doodle

The OpenSCAD source code as a GitHub Gist:

// Window Muntin Clips
// Ed Nisley KE4ZNU June 2022
Layout = "Show"; // [Build, Show]
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
ID = 0;
OD = 1;
LENGTH = 2;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
//----------------------
// Dimensions
ClipOA = [13.0,18.7,8.0];
TongueAngle = 70;
TongueOA = [14.0,10.0,1.8 - 0.2]; // minus Z windage for angular slices
BuildGap = 5.0;
//----------------------
// Useful routines
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);
}
//----------------------
// Pieces
module Shell() {
// Clip base as 2D polygon
// Magic numbers from measurements
cx = ClipOA.x;
cy = ClipOA.y;
cz = ClipOA.z;
ClipPts = [
[0,0],[0,cz],[0.3,cz],
[1.0,cz-1.0],[2.0,cz-2.3],[2.0,cz-3.0],[1.3,cz-3.5],
[1.3,1.6],[17.4,1.6],
[17.4,cz-3.5],[16.7,cz-3.0],[16.7,cz-2.3],[17.7,cz-1.0],
[18.4,cz],[18.7,cz],[18.7,0.0],[0,0]
];
difference() {
translate([-ClipOA.x,-ClipOA.y/2,0])
rotate([90,0,90])
linear_extrude(height=ClipOA.x,convexity=3)
polygon(convexity=3,points=ClipPts);
translate([-(ClipOA.x - 3.0/2 + Protrusion),0,0])
cube([3.0 + Protrusion,ClipOA.y - 2*1.3,4*1.6],center=true);
}
}
module Tongue() {
tx = TongueOA.x;
ty = TongueOA.y;
tz = TongueOA.z;
tt = ty - 2*sqrt(2)*tz; // width at top of tapers
td = ThreadWidth; // min size of features
intersection() {
rotate([0,-TongueAngle,0]) {
difference() {
union() {
hull() {
for (j=[-1,1]) {
translate([td/2,j*(ty - td)/2,td/2])
cube(td,center=true);
translate([td/2,j*(tt - td)/2,tz - td/2])
cube(td,center=true);
}
translate([10.0,0,0])
rotate(180/12)
cylinder(d=ty,h=td,center=false,$fn=12);
translate([10.0,0,tz - td/2])
rotate(180/12)
cylinder(d=tt,h=td,center=false,$fn=12);
};
translate([10.0,0,-5.2])
rotate(180/12)
cylinder(d=5.0,h=5.2,center=false,$fn=12);
translate([10.0,0,-5.2])
rotate(180/12)
resize([0,0,2.0])
sphere(d=5.0/cos(180/12),$fn=12);
}
if (false)
translate([10.0,0,-10]) // stiffening hole
rotate(180/6)
PolyCyl(0.1,20,6);
}
}
cube([2*ClipOA.x,2*ClipOA.y,2*IntegerMultiple(13.0,ThreadThick)],center=true);
}
}
module Clip() {
Shell();
Tongue();
}
//----------------------
// Build it
if (Layout == "Show") {
Clip();
}
if (Layout == "Build") {
Clip();
}

Garden Cart Handle Pivot

For reasons not relevant here, I was tapped to replace the plastic parts attaching the handle to a garden cart:

Garden Cart - handle attachment
Garden Cart – handle attachment

The owner tried to contact the “manufacturer” to no avail; repair parts are simply not available, even if the name painted on the cart had a meaningful relationship to anything else.

Well, I can fix that:

Garden Cart - handle repair parts
Garden Cart – handle repair parts

Fortunately, another cart in the fleet provided the missing bits so I could reverse-engineer their measurements.

The solid model looks about like you’d expect:

Garden Cart Handle - show view
Garden Cart Handle – show view

Printing the two halves with those nice (yellow) bosses in place wasn’t feasible. They were exactly 1 inch in diameter, so I just parted two cookies from the end of a stout acetal rod after drilling a hole for the 2-¼ inch 5/16-18 bolt.

The two pieces took nigh onto three hours with five perimeters and 50% infill:

Garden Cart Handle - slicer preview
Garden Cart Handle – slicer preview

While delivering and installing the parts, I got volunteered to haul plants to cars with one of the carts during the upcoming Spring Plant Sale. That’ll teach me to stay in the Basement Shop …

The OpenSCAD source code as a GitHub Gist:

// Garden Cart Handle Pivot
// Ed Nisley KE4ZNU 2022-05
Layout = "Show"; // [Show,Build]
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
ID = 0;
OD = 1;
LENGTH = 2;
//----------
// Dimensions
// Handle lies along X axis
HandleOD = (7/8) * inch;
BoltOD = (5/16) * inch;
Washer = [BoltOD,1.0 * inch,2.0]; // just for Show
Disk = [BoltOD,62.0,(3/16) * inch];
ClampBase = [(1 + 7/8)*inch,(1 + 1/8)*inch,2.0];
Kerf = 2.0;
CornerRadius = 1.0;
PivotOA = [Disk[OD],Disk[OD],HandleOD + 2*ClampBase.z + 2*Disk[LENGTH]];
//----------------------
// Useful routines
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(d=(FixDia + HoleWindage),h=Height,$fn=Sides);
}
//----------------------
// Set up parts
module Handle() {
translate([-2*PivotOA.x,0,0])
rotate([0,90,0])
PolyCyl(HandleOD,4*PivotOA.x,24);
}
module Bolt() {
translate([0,0,-PivotOA.z])
PolyCyl(BoltOD,2*PivotOA.z,12);
}
module Pivot() {
difference() {
union() {
hull()
for (i=[-1,1], j=[-1,1]) // rounded block
translate([i*(ClampBase.x/2 - CornerRadius),j*(ClampBase.y/2 - CornerRadius),-PivotOA.z/2])
cylinder(r=CornerRadius,h=PivotOA.z,$fn=4*8);
for (k=[-1,1])
translate([0,0,k*(PivotOA.z/2 - Disk[LENGTH]/2)])
rotate(180/36)
cylinder(d=Disk[OD],h=Disk[LENGTH],$fn=36,center=true);
}
Handle();
Bolt();
cube([2*ClampBase.x,2*ClampBase.y,Kerf],center=true); // slice through center
}
}
//----------
// Build them
if (Layout == "Show") {
rotate([90,-45,0]) {
Pivot();
color("Green")
translate([2*PivotOA.x - PivotOA.x/2,0,0])
Handle();
color("Red")
Bolt();
color("Yellow")
for (k=[-1,1])
translate([0,0,k*(PivotOA.z/2 + Washer[LENGTH])])
rotate(180/36)
cylinder(d=Washer[OD],h=Washer[LENGTH],$fn=36,center=true);
}
}
if (Layout == "Build") {
Offset = 5.0;
intersection() {
translate([-(PivotOA.x/2 + Offset),0,PivotOA.z/2])
Pivot();
translate([-2*PivotOA.x,-2*PivotOA.y,0])
cube([4*PivotOA.x,4*PivotOA.y,PivotOA.z/2],center=false);
}
intersection() {
translate([(PivotOA.x/2 + Offset),0,PivotOA.z/2])
rotate([180,0,0])
Pivot();
translate([-2*PivotOA.x,-2*PivotOA.y,0])
cube([4*PivotOA.x,4*PivotOA.y,PivotOA.z/2],center=false);
}
}

OMTech 60 W Laser: Improved COB LED Shades

Adding (fake) rivets to the COB LED shade brackets definitely improves their appearance:

Acrylic COB LED Shade - installed
Acrylic COB LED Shade – installed

I cut new shades from vintage clear acrylic sheet, with more aluminized mylar attached to the lower surface: you can barely see the COB LED strip through the reflecting surface.

Depending on how you arrange all the hardware hanging on the nozzle, the shades can collide with something at the home position in the far right corner:

Acrylic COB LED Shade - X clearance
Acrylic COB LED Shade – X clearance

Definitely a step up from cardboard …

OMTech 60 W Laser: Laser Power Indicator

Although the OMTech laser controls the laser power supply with a key-lock switch, there’s little visible difference between the OFF and ON positions. Having occasionally mistaken it in both directions, this seemed like a useful addition:

Laser Power Lock Indicator - installed
Laser Power Lock Indicator – installed

The strip of black duct tape below the lock muffles the rattle of the triangle hatch key against the metal cabinet.

Two snippets of foam tape hold the knob to the lock cylinder, making an admittedly tenuous connection, but the knob fits around the outside of the switch housing with minimal clearance and doesn’t shouldn’t suffer any torque or pulling, so it might work.

The solid model looks about like you’d expect:

Laser Power Lock Indicator - solid model
Laser Power Lock Indicator – solid model

Unfortunately, it has no good orientation for printing, so I let PrusaSlicer generate support material inside the knob:

Laser Power Lock Indicator - Support structures
Laser Power Lock Indicator – Support structures

Suffice it to say: removing all that plastic did not go well.

I eventually grabbed the knob in the lathe and bored the interior out to its more-or-less proper dimensions, figuring nobody would ever notice the carnage, and it worked reasonably well. In the unlikely event I need another pointer, I’ll add a support spider to hold up the interior with minimal contact and less plastic.

Yeah, the laser really needs a stack light showing its condition and safety status …

The OpenSCAD source code as a GitHub Gist:

// Indicator for OMTech laser power lock
// Ed Nisley KE4ZNU 2022-04-09
KnobOD = 35.0;
KnobHeight = 22.0;
KnobTaper = 4.0;
PointerLength = 45.0;
PointerThick = 3.0;
TipOD = 2.0;
/* [Hidden] */
//------
Protrusion = 0.1; // make holes end cleanly
HoleWindage = 0.2;
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(d=(FixDia + HoleWindage),h=Height,$fn=Sides);
}
//----------
// Create part
// Plenty of magic numbers from actual measurements
module Pointer() {
difference() {
union() {
linear_extrude(height=PointerThick)
hull() {
circle(d=KnobOD,$fn=24);
translate([PointerLength - TipOD/2,0])
circle(d=TipOD,$fn=12);
}
cylinder(d=KnobOD,h=KnobHeight - KnobTaper,$fn=24);
translate([0,0,KnobHeight - KnobTaper - Protrusion])
cylinder(d1=KnobOD,d2=KnobOD - 3.0,h=KnobTaper + Protrusion,$fn=24);
}
translate([0,0,-Protrusion]) {
PolyCyl(29.0,14.0 + Protrusion,24);
PolyCyl(24.0,14.0 + 5.0 + Protrusion,24); // leaves clearance under pointer
}
translate([0,0,KnobHeight])
cube([12.0,2.0,2*KnobHeight],center=true);
}
}
//----------
// Build it
Pointer();

And doodles giving the dimensions of the key lock, not all of which can be true at the same time:

Laser Power Lock Indicator - Dimension Doodles
Laser Power Lock Indicator – Dimension Doodles

OMTech 60 W Laser: COB LED Shades

Adding LED strips around the interior of the laser platform definitely improved the visibility of things on the honeycomb platform:

OMTech 60W laser - COB LED strips
OMTech 60W laser – COB LED strips

However, all that upward-directed light goes directly into my glare-sensitive eyeballs, so I added shades above the strips:

COB LED Shade - installed
COB LED Shade – installed

They’re cut from corrugated cardboard because I have an essentially infinite supply and I’m still working out speeds and intensities. Eventually they’ll become something like black acrylic.

The brackets emerged from the vasty digital deep through the miracle of 3D printing:

COB LED Shade Brackets - slice preview
COB LED Shade Brackets – slice preview

They’re stuck to the laser cabinet and the cardboard with double-sided duct tape. If you’re careful, they will line up along one edge of the tape, roll over neatly to stick their other face, then a single razor knife cut can separate each pair of neighbors.

The underside sports an aluminized mylar strip to redirect the wasted light in a more useful direction:

COB LED Shade - aluminized Mylar reflector
COB LED Shade – aluminized Mylar reflector

The tapeless sticky shipped with the laser holds the reflector in place, while its 20 mm width sets the 21 mm shade dimension. Although you want a reasonably smooth layer, it need not be mirror-flat.

Now it’s really bright in there:

COB LED Shade - overview
COB LED Shade – overview

While I had my head under the hood, I stuck a fourth strip of COB LEDs on the lip along the rear edge of the opening; it’s bright enough to cast the shadow just forward of the laser head despite the OEM under-gantry LED strip. Because the rear strip is aimed downward, it didn’t need a shade.

The perforated cardboard sheet on the left is a spike plate: more about that later.

The SVG drawings as a GitHub Gist:

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

The OpenSCAD source code as a GitHub Gist:

// Bracket for COB LED shade
// Ed Nisley KE4ZNU 2022-03-24
BaseLength = 20.0;
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
Protrusion = 0.1; // make holes end cleanly
WebThick = 4*ThreadWidth;
BasePlate = [BaseLength,5*WebThick,WebThick];
//----------
// Create parts
module Bracket() {
R = BaseLength/3;
N = 36;
union() {
rotate([90,0,0])
translate([0,0,-WebThick/2])
linear_extrude(height=WebThick,convexity=2)
difference() {
intersection() {
union () {
square(2*R,center=false);
translate([0,2*R])
rotate(180/N)
circle(r=R,$fn=N);
translate([2*R,0])
rotate(180/N)
circle(r=R,$fn=N);
}
square(3*R,center=false);
}
translate([2*R*cos(180/N),2*R*cos(180/N)])
rotate(180/N)
circle(r=R,$fn=N);
}
rotate([0,-90,0])
translate([0,-BasePlate.y/2,-BasePlate.z])
cube(BasePlate,center=false);
translate([0,-BasePlate.y/2,0])
cube(BasePlate,center=false);
}
}
//----------
// Build them
Bracket();

B4-size Light Pad: Stabilizing the USB Connector

What used to be a “light box” had become a “light pad” powered through a USB Micro-B connector on the side. Unfortunately, the pad’s 5 mm thickness allows for very little mechanical reinforcement around the USB jack, while providing infinite opportunity to apply bending force. Over the course of the last half-dozen years (during which the price has dropped dramatically, despite recent events), the slightest motion flickered the LEDs.

So I squished the jack’s metal shell back into shape, found a short right-angle USB cable, and conjured a reinforcing fixture from the vasty digital deep:

LitUp LED Light Pad
LitUp LED Light Pad

The plate fits under the light pad, where a strip of super-sticky duct tape holds it in place:

LitUp Light Pad USB jack reinforcement - bottom
LitUp Light Pad USB jack reinforcement – bottom

The USB plug fits between the two blocks with hot-melt glue holding it in place and filling the gap between the plug and the pad.

I’d like to say it’s more elegant than the cable redirection for my tablet, but anything involving black electrical tape and hot-melt glue just isn’t in the running for elegant:

LitUp Light Pad USB jack reinforcement - top
LitUp Light Pad USB jack reinforcement – top

On the other paw, that socket ought to last pretty nearly forever, which counts for a whole lot more around here.

The retina-burn orange tape patches on the connector eliminate all the fumbling inherent to an asymmetric connector with invisible surface features. The USB wall wart on the other end of the cable sports similar markings.

The OpenSCAD source code as a GitHub Gist:

// Bracket to protect USB jack on LitUp LED Pad
// Ed Nisley KE4ZNU 2022-03-28
Protrusion = 0.1; // make holes end cleanly
Pad = [10.0,30.0,1.2];
Plug = [8.0,10.5 + 0.5,8.0];
BasePlate = [Pad.x + Plug.x,Pad.y,Pad.z];
//----------
// Create parts
module Stiffener() {
difference() {
union() {
translate([-Pad.x,-BasePlate.y/2,0])
cube(BasePlate,center=false);
translate([0,-Pad.y/2,0])
cube([Plug.x,Pad.y,Plug.z],center=false);
}
translate([-Protrusion,-Plug.y/2,-Protrusion])
cube(Plug + [2*Protrusion,0,Plug.z],center=false);
}
}
//----------
// Build them
Stiffener();

ShopVac Nozzle Caddy

Shortly after acquiring the Greatest ShopVac, I zip-tied half a foot of cardboard tube to the handle to corral the nozzle and keep the ungainly hose from sprawling across the floor. While disembowling the Ottlite into a mini-lathe light, the plastic trim joining the baseplate to the vertical tube cried out to become a nozzle caddy:

ShopVac Nozzle Caddy - front view
ShopVac Nozzle Caddy – front view

It was exactly the right size and shape (by my admittedly slack standards) to hold the nozzle, plus being destined for the trash, so all it needed was a pair of clamp brackets conjured from the vasty digital deep:

ShopVac Nozzle Caddy - solid model
ShopVac Nozzle Caddy – solid model

The bosses fit into a tapered slot along what was the rear side, with a pair of 4 mm holes at each end for screws into threaded brass inserts epoxied into the brackets:

ShopVac Nozzle Caddy - clamps mounted
ShopVac Nozzle Caddy – clamps mounted

They obviously descend from the many clamp mounts I’ve made for everything from garden hoses to bike running lights. A pair of 4 mm SHCS squish the clamp around the handle, with a strip of electrical tape improving plastic-to-metal griptivity:

ShopVac Nozzle Caddy - side view
ShopVac Nozzle Caddy – side view

The clearance just barely allows a nylock nut atop a washer and you’ll want to trim those 40 mm screws to an exact fit, but it came out pretty well.

The original dimension doodle with some modeling ideas that didn’t survive more thinking:

ShopVac Nozzle Caddy - Dimension Doodle 1
ShopVac Nozzle Caddy – Dimension Doodle 1

A more detailed doodle with brass inserts instead of the nylock nuts and an aluminum spreader plate that was obviously not necessary:

ShopVac Nozzle Caddy - Dimension Doodle 2
ShopVac Nozzle Caddy – Dimension Doodle 2

In retrospect, the inserts would make more sense.

The angle doodles convinced me not to bother modeling either the slot’s taper along its length or its mold draft.

Kinda looks like it grew there and makes one wonder why they don’t include a caddy as a standard option.

The OpenSCAD source code as a GitHub Gist:

// ShopVac Nozzle Caddy
// Ed Nisley KE4ZNU 2022-02
Layout = "Show"; // [Handle,Block,Show,Build]
HandleOD = 20.0;
//- Extrusion parameters must match reality!
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
ID = 0;
OD = 1;
LENGTH = 2;
//----------
// Dimensions
// Handle lies along X axis
Handle = [200,HandleOD,HandleOD]; // X = longer than anything else
WallThick = 5.0; // Thinnest printed wall
Screw = [4.0,7.0,25.0]; // M4 socket head cap screw
Washer = [4.5,9.0,0.8]; // M4 washer
Insert = [4.0,5.9,10.0]; // M4 brass insert
Block = [15.0,Handle.y + 4*WallThick + 2*Screw[ID],HandleOD + 2*WallThick]; // overall clamp block
echo(str("Block: ",Block));
Bosses = [[Block.x,9.5,13.0],[Block.x,15.0,9.0]];
ScrewOC = Handle.y + 2*WallThick + Screw[ID];
Kerf = 1.0; // cut through middle to apply compression
Gap = 1.25;
CornerRadius = Washer[OD]/2;
//----------------------
// Useful routines
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(d=(FixDia + HoleWindage),h=Height,$fn=Sides);
}
// Shopvac handle
module Handle() {
rotate([0,90,0])
translate([0,0,-Handle.x/2])
rotate(180/(4*8))
PolyCyl(Handle.y,Handle.x,4*8);
}
// Clamp block
module ClampBlock(BossID=0) {
difference() {
union() {
hull()
for (i=[-1,1], j=[-1,1]) // rounded block
translate([i*(Block.x/2 - CornerRadius),j*(Block.y/2 - CornerRadius),-Block.z/2])
cylinder(r=CornerRadius,h=Block.z,$fn=8);
translate([0,0,-(Block.z/2 + Bosses[BossID].z/2 - Protrusion)])
cube(Bosses[BossID],center=true);
}
for (j = [-1,1]) // screw holes
translate([0,j*ScrewOC/2,-(Block.z/2 + Protrusion)])
rotate(180/6)
PolyCyl(Screw[ID],Block.z + 2*Protrusion,6);
cube([2*Block.x,2*Block.y,Kerf],center=true);
Handle();
translate([0,0,-Block.z])
rotate(180/6)
PolyCyl(Screw[ID],Block.z,6);
translate([0,0,-(Handle.z/2 + Insert[LENGTH])])
rotate(180/6)
PolyCyl(Insert[OD],Handle.y,6);
}
}
// Splice block less handle bore
module ShapedBlock() {
difference() {
ClampBlock();
Handle();
}
}
//----------
// Build them
if (Layout == "Handle")
Handle();
if (Layout == "Block")
ClampBlock(BossID=0);
if (Layout == "Show") {
color("Green",0.25)
Handle();
xofs = -((len(Bosses) - 1)/2 * Gap*Block.x);
for (i=[0:len(Bosses) - 1])
translate([xofs + i*Gap*Block.x,0,0])
ClampBlock(i);
}
if (Layout == "Build") {
yofs = -((len(Bosses) - 1)/2 * Gap*Block.y);
for (j=[0:len(Bosses) - 1])
translate([0,yofs + j*Gap*Block.y,0])
translate([0,0,Block.x/2])
rotate([0,90,0])
ClampBlock(j);
}