Coaster Generator: Simple Petals

Having figured out how to intersect a line with a circle, I figured I could do it twice to put arcs on both the inside and the outside of each petal:

Chipboard coaster - double arcs
Chipboard coaster – double arcs

As before, scribbling markers on plain chipboard makes for a … subdued … coaster, so I tried chipboard with one white surface:

Chipboard coaster - plain vs white
Chipboard coaster – plain vs white

Much better.

Clamping the coaster produces a flatter result:

Chipboard coaster - clamping
Chipboard coaster – clamping

With the risk of squishing excess glue through the kerf:

Chipboard coaster - excess glue
Chipboard coaster – excess glue

That’s the same coaster as in the first picture, carefully arranged with light reflecting off the flat glue surface. In real life, the nearly transparent glue doesn’t look nearly so awful, but smoothing much less glue than seems necessary across the bottom disk suffices.

The geometry doodle with the arcs:

Chipboard coaster - double arc petal geometry doodle
Chipboard coaster – double arc petal geometry doodle

I suppose I should refactor the code with a quadratic solver returning a list of roots, but copypasta suffices for now.

The GCMC and Bash source code as a GitHub Gist:

#!/bin/bash
# Simple petals test piece
# Ed Nisley KE4ZNU - 2022-07-01
Flags='-P 4 --pedantic' # quote to avoid leading hyphen gotcha
SVGFlags='-P 4 --pedantic --svg --svg-no-movelayer --svg-opacity=1.0 --svg-toolwidth=0.2'
# Set these to match your file layout
ProjPath='/mnt/bulkdata/Project Files/Laser Cutter/Coasters/Source Code'
LibPath='/opt/gcmc/library'
ScriptPath=$ProjPath
Script='Simple Petals.gcmc'
[ -z "$1" ] && petals="6" || petals="$1"
fn=Petals-$petals.svg
echo Output: $fn
gcmc $SVGFlags \
-D "NumPetals=$petals" \
--include "$LibPath" \
"$ScriptPath"/"$Script" > "$fn"
view raw petals.sh hosted with ❤ by GitHub
// Simple Petals Test Piece
// Ed Nisley KE4ZNU
// 2022-07-12 Simplest possible petals
layerstack("Frame","Petals","Rim","Base","Center","Tool1"); // SVG layers map to LightBurn colors
//-----
// Library routines
include("tracepath.inc.gcmc");
include("tracepath_comp.inc.gcmc");
include("varcs.inc.gcmc");
include("engrave.inc.gcmc");
FALSE = 0;
TRUE = !FALSE;
//-----
// Command line parameters
// -D various useful tidbits
// add unit to speeds and depths: 2000mm / -3.00mm / etc
if (!isdefined("OuterDia")) {
OuterDia = 100.0mm;
}
if (!isdefined("CenterDia")) {
CenterDia = 25.0mm;
}
if (!isdefined("NumPetals")) {
NumPetals = 6;
}
if (!isdefined("Sash")) {
Sash = 5.0mm;
}
// Petal values
PetalAngle = 360.0deg/NumPetals; // subtended by inner sides
PetalHA = PetalAngle/2;
PetalOD = OuterDia - 2*Sash;
PetalID = CenterDia + 2*Sash;
PetalOAL = OuterDia/2 - Sash - (Sash/2)/sin(PetalHA);
//message("petalOAL: ",PetalOAL);
// Find petal vertices
P0 = [(Sash/2) / sin(PetalHA),0.0mm];
t1 = tan(PetalHA);
sc = (Sash/2) / cos(PetalHA);
if (P0.x < PetalID/2) {
a = 1 + pow(t1,2);
b = -2 * t1 * sc;
c = pow(sc,2) - pow(PetalID/2,2);
xp = (-b + sqrt(pow(b,2) - 4*a*c))/(2*a);
xn = (-b - sqrt(pow(b,2) - 4*a*c))/(2*a);
y = xp*t1 - sc;
if (FALSE) {
message("a: ",a);
message("b: ",b);
message("c: ",c);
message("p: ",xp," n: ",xn," y: ",y);
}
P1 = [xp,y];
}
else {
P1 = P0;
}
a = 1 + pow(t1,2);
b = -2 * t1 * sc;
c = pow(sc,2) - pow(PetalOD/2,2);
if (FALSE) {
message("a: ",a);
message("b: ",b);
message("c: ",c);
}
xp = (-b + sqrt(pow(b,2) - 4*a*c))/(2*a);
xn = (-b - sqrt(pow(b,2) - 4*a*c))/(2*a);
y = to_mm(sqrt(pow(PetalOD/2,2) - pow(xp,2)));
//message("p: ",xp," n: ",xn," y: ",y);
P2 = [xp,y];
PetalWidth = 2*P2.y;
P3 = [PetalOD/2,0.0mm];
if (FALSE) {
message("P0: ",P0);
message("P1: ",P1);
message("P2: ",P2);
message("P3: ",P3);
}
// Construct paths
PetalPoints = {P1,P2};
OutArc = varc_cw([P2.x,-P2.y] - P2,PetalOD/2);
OutArc += P2;
PetalPoints += OutArc;
if (P0 != P1) {
PetalPoints += {[P1.x,-P1.y]};
InArc = varc_ccw(P1 - [P1.x,-P1.y],PetalID/2);
InArc += [P1.x,-P1.y];
PetalPoints += InArc;
}
else {
PetalPoints += {P0};
}
//--- Lay out the frame
linecolor(0xff0000);
layer("Frame");
if (CenterDia) {
goto([CenterDia/2,0mm]);
circle_cw([0mm,0mm]);
}
repeat(NumPetals;i) {
a = (i-1)*PetalAngle;
tracepath(rotate_xy(PetalPoints,a));
}
goto([OuterDia/2,0]);
circle_cw([0mm,0mm]);
//--- Lay out internal pieces for oriented cutting
// baseplate
layer("Base");
relocate([OuterDia + 2*Sash,0]);
goto([OuterDia/2,0]);
circle_cw([0mm,0mm]);
// central circle
if (CenterDia) {
layer("Center");
relocate([OuterDia/2 + Sash,-(OuterDia - CenterDia)/2]);
goto([CenterDia/2,0mm]);
circle_cw([0mm,0mm]);
}
// petals
layer("Petals");
repeat(NumPetals;i) {
org = [PetalWidth/2 - OuterDia/2,-(OuterDia + Sash)];
relocate([(i-1)*(PetalWidth + Sash) + org.x,org.y]);
tracepath(rotate_xy(PetalPoints,90deg));
}
// Debugging by printf()
if (FALSE) {
layer("Tool1");
linecolor(0xff1f00);
goto([Sash/2,0mm]);
circle_cw([0mm,0mm]);
goto(P0);
circle_cw([0mm,0mm]);
goto([0,0]);
move([OuterDia/2,0]);
goto([0,0]);
move(OuterDia/2 * [cos(PetalHA),sin(PetalHA)]);
goto(P2);
move_r([0,-PetalWidth/2]);
}

Laser Kerf Width Test Pattern / Coaster Generator

Before trying to make decorative coasters from colorful acrylic, I figured a few practice sessions in chipboard would be in order:

Chipboard coasters
Chipboard coasters

They’re colored with wide tip Sharpies of various ages and, as the yellow and uncolored sections show, chipboard never gets very bright. On the other paw, chipboard is also known as “beer mat”, so at least I have the right general idea.

The patterns come from a GCMC program producing SVG figures for LightBurn to apply kerf compensation:

Chipboard coasters - cut and color
Chipboard coasters – cut and color

It’s obviously too late to have me color within the lines.

The overall frame in the upper left and the base plate in the upper right get the kerf compensation, which (for chipboard) turns out to be +0.15 mm outward (thus making the holes smaller and the diameter larger). If I were doing marquetry, I’d want to arrange each piece on a separate wood veneer sheet with proper grain orientation and similar fussiness, but that’s not the point right now.

Without compensation, the pieces have a drop-in fit with an obvious gap:

Coaster - chipboard - no kerf comp
Coaster – chipboard – no kerf comp

Adding a mere 0.15 mm on each side produces a very snug fit:

Coaster - chipboard - frame 0.15 out
Coaster – chipboard – frame 0.15 out

In fact, the pieces go in from the back and require hammering gentle tapping to persuade all the corners into place.

Protip: putting a dark color on the frame and around the edges conceals many flaws.

Increasing the compensation to +0.20 mm means the pieces no longer fit and, when eventually battered into the frame, the surface becomes a concave-upward dish.

With the (colored) pieces in the frame, I covered the base plate with a thin layer of good old Elmer’s Yellow Wood Glue, dropped the top over it with some attention to good alignment on all sides, and clamped the assembly between two planks for a while. Obviously, you’d want to make more than one at a time, but they’re rather labor intensive.

The GCMC program produces the patterns from the coaster’s dimensions:

  • Outer diameter
  • Number of leaves around the center
  • Center spot diameter
  • Sash width (it’s really a muntin, but quilters say sash)
  • Leaf aspect ratio (max width / overall length)

Due to the relentless symmetry, finding the points describing half a leaf and half the sector between two leaves suffices to generate the entire coaster by various rotations around the center. The code performs no error checking whatsoever, so some dimensions emit a hard crash rather than a coaster.

A geometry doodle with some incorrect values:

Coaster Geometry doodle
Coaster Geometry doodle

Poinr P1 (where the leaf snugs against the circular sash around the center spot) sits at the intersection of a line and a circle, so the code solves a quadratic equation with grisly coefficients:

  a = 1 + pow(tan(LeafStemHA),2);
  b = -2 * tan(LeafStemHA) * (Sash/2) / cos(LeafStemHA);
  c = pow((Sash/2) / cos(LeafStemHA),2) - pow(LeafID/2,2);
  xp = (-b + sqrt(pow(b,2) - 4*a*c))/(2*a);
  xn = (-b - sqrt(pow(b,2) - 4*a*c))/(2*a);
  y = xp*tan(LeafStemHA) - (Sash/2) / cos(LeafStemHA);
  P1 = [xp,y];

Given the geometry, the “plus” root is always the one to use.

A doodle working out that intersection, as well as for P5 out at the widest part of the leaf, carrying some errors from the geometry doodle:

Coaster Geometry equations
Coaster Geometry equations

Both of those doodles have errors; the GCMC source code remains the final arbiter of coaster correctness.

The Bash and GCMC source code as a GitHub Gist:

#!/bin/bash
# Marquetry test piece
# Ed Nisley KE4ZNU - 2022-07-01
Flags='-P 4 --pedantic' # quote to avoid leading hyphen gotcha
SVGFlags='-P 4 --pedantic --svg --svg-no-movelayer --svg-opacity=1.0 --svg-toolwidth=0.2'
# Set these to match your file layout
ProjPath='/mnt/bulkdata/Project Files/Laser Cutter/Marquetry/Source Code'
LibPath='/opt/gcmc/library'
ScriptPath=$ProjPath
Script='Marquetry Test Piece.gcmc'
leaves="NumLeaves=$1"
aspect="LeafAspect=$2"
fn=Marq-$1-$2.svg
echo Output: $fn
gcmc $SVGFlags -D "$leaves" -D "$aspect" \
--include "$LibPath" \
"$ScriptPath"/"$Script" > "$fn"
view raw marq.sh hosted with ❤ by GitHub
// Marquetry Laser Cuttery Test Piece
// Ed Nisley KE4ZNU
// 2022-07-01 Simplest possible mandala
layerstack("Frame","Leaves","Rim","Base","Center","Tool1"); // SVG layers map to LightBurn colors
//-----
// Library routines
include("tracepath.inc.gcmc");
include("tracepath_comp.inc.gcmc");
include("varcs.inc.gcmc");
include("engrave.inc.gcmc");
FALSE = 0;
TRUE = !FALSE;
//-----
// Command line parameters
// -D various useful tidbits
// add unit to speeds and depths: 2000mm / -3.00mm / etc
if (!isdefined("OuterDia")) {
OuterDia = 120.0mm;
}
if (!isdefined("CenterDia")) {
CenterDia = 20.0mm;
}
if (!isdefined("NumLeaves")) {
NumLeaves = 5;
}
if (!isdefined("Sash")) {
Sash = 4.0mm;
}
if (!isdefined("LeafAspect")) {
LeafAspect = 0.40;
}
// Leaf values
LeafStemAngle = 360.0deg/NumLeaves; // subtended by inner sides
LeafStemHA = LeafStemAngle/2;
LeafLength = OuterDia/2 - Sash - (Sash/2)/sin(LeafStemHA);
LeafWidth = LeafAspect*LeafLength;
L1 = (LeafWidth/2)/tan(LeafStemHA);
L2 = LeafLength - L1;
// message("Len: ",LeafLength," L1: ",L1," L2: ",L2);
LeafTipHA = to_deg(atan(LeafWidth/2,L2)); // subtended by outer sides
LeafTipAngle = 2*LeafTipHA;
// message("Width: ",LeafWidth);
// message("Tip HA: ",LeafTipHA);
LeafID = CenterDia + 2*Sash;
LeafOD = LeafID + LeafLength;
// message("ID: ",LeafID," OD: ",LeafOD);
// Find leaf and rim vertices
P0 = [(Sash/2) / sin(LeafStemHA),0.0mm];
if (P0.x < LeafID/2) {
a = 1 + pow(tan(LeafStemHA),2);
b = -2 * tan(LeafStemHA) * (Sash/2) / cos(LeafStemHA);
c = pow((Sash/2) / cos(LeafStemHA),2) - pow(LeafID/2,2);
// message("a: ",a);
// message("b: ",b);
// message("c: ",c);
xp = (-b + sqrt(pow(b,2) - 4*a*c))/(2*a);
xn = (-b - sqrt(pow(b,2) - 4*a*c))/(2*a);
y = xp*tan(LeafStemHA) - (Sash/2) / cos(LeafStemHA);
// message("p: ",xp," n: ",xn," y: ",y);
P1 = [xp,y];
}
else {
P1 = P0;
}
P2 = P0 + [L1,LeafWidth/2];
P3 = P0 + [LeafLength,0mm];
P4 = P3 + [Sash/sin(LeafTipHA),0.0mm];
P5r = P4.x * sin(LeafTipHA) / sin(180deg - LeafStemHA - LeafTipHA);
P5 = rotate_xy([P5r,0.0mm],LeafStemHA);
P6 = rotate_xy(P4,LeafStemAngle);
t2 = pow(tan(-LeafTipHA),2);
a = 1 + t2;
b = -2 * t2 * P4.x;
c = t2 * pow(P4.x,2) - pow(P3.x,2);
xp = (-b + sqrt(pow(b,2) - 4*a*c))/(2*a);
xn = (-b - sqrt(pow(b,2) - 4*a*c))/(2*a);
y = (xp - P4.x)*tan(-LeafTipHA);
// message("p: ",xp," n: ",xn," y: ",y);
P4a = [xp,y];
P6a = rotate_xy(P4a,LeafStemAngle - 2*atan(P4a.y,P4a.x));
// message("P4a: ",P4a);
// message("P6a: ",P6a);
// message("P0: ",P0);
// message("P1: ",P1);
// message("P2: ",P2);
// message("P3: ",P3);
// message("P4: ",P4);
// message("P5: ",P5);
// message("P6: ",P6);
// Construct paths
LeafPoints = {P1,P2,P3,[P2.x,-P2.y],[P1.x,-P1.y]};
if (P0 != P1) {
StemArc = varc_ccw(P1 - [P1.x,-P1.y],LeafID/2);
StemArc += [P1.x,-P1.y];
LeafPoints += StemArc;
}
RimChord = length(P4a - P6a);
RimThick = OuterDia/2 - Sash - length(P5);
RimPoints = {P4a,P5,P6a};
RimArc = varc_cw(P4a - P6a,P4a.x);
RimArc += P6a;
RimPoints += RimArc;
//--- Lay out the frame
linecolor(0xff0000);
layer("Frame");
goto([CenterDia/2,0mm]);
circle_cw([0mm,0mm]);
repeat(NumLeaves;i) {
a = (i-1)*LeafStemAngle;
tracepath(rotate_xy(LeafPoints,a));
}
repeat(NumLeaves;i) {
a = (i-1)*LeafStemAngle;
tracepath(rotate_xy(RimPoints,a));
}
linecolor(0xff0000);
goto([OuterDia/2,0]);
circle_cw([0mm,0mm]);
//--- Lay out internal pieces for oriented cutting
// baseplate
layer("Base");
relocate([OuterDia + 2*Sash,0]);
goto([OuterDia/2,0]);
circle_cw([0mm,0mm]);
// central circle
layer("Center");
relocate([OuterDia/2 + Sash,-(OuterDia - CenterDia)/2]);
goto([CenterDia/2,0mm]);
circle_cw([0mm,0mm]);
// leaves
layer("Leaves");
repeat(NumLeaves;i) {
org = [LeafWidth/2 - OuterDia/2,-(OuterDia + Sash)];
relocate([(i-1)*(LeafWidth + Sash) + org.x,org.y]);
tracepath(rotate_xy(LeafPoints,90deg));
}
// rim
layer("Rim");
repeat(NumLeaves;i) {
org = [-Sash,-(OuterDia + 2*Sash + RimChord/2)];
relocate([(i-1)*(RimThick + Sash) + org.x,org.y]);
tracepath(rotate_xy(RimPoints,180 - LeafStemHA));
}
// Debugging by printf()
if (FALSE) {
layer("Tool1");
linecolor(0xff1f00);
goto([Sash/2,0mm]);
circle_cw([0mm,0mm]);
goto(P0);
circle_cw([0mm,0mm]);
goto([0,0]);
move([OuterDia/2,0]);
goto([0,0]);
move(OuterDia/2 * [cos(LeafStemHA),sin(LeafStemHA)]);
goto(P2);
move_r([0,-LeafWidth/2]);
}

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

OMTech 60 W Laser: Plant Markers

While calibrating the laser’s scan offset, I also tried various fonts:

Offset cal - text - overview
Offset cal – text – overview

Putting two lines of the most-readable font inside an outline reverse-engineered from a few handwritten samples let me cut out a bunch of plant markers from white-on-black Trolase acrylic:

Plant Markers - cutting
Plant Markers – cutting

Which look downright dignified in real life:

Plant markers - African Violet
Plant markers – African Violet

Admittedly, sweet potato slips don’t require such extensive documentation:

Plant Markers - sweet potatoes
Plant Markers – sweet potatoes

Cutting the sheet flat on the honeycomb platform worked well, modulo Sadler’s warning about cutting acrylic, and a few smudges on the back of the markers will go unnoticed.

This was actually an excuse to use LightBurn’s Variable Text feature, so the tags contain formatting codes:

Plant Markers - Variable Text template
Plant Markers – Variable Text template

The codes give the position and format for text fields in a CSV file containing one line for each tag:

Austrocylindropuntia subulata,Eve’s Pin Cactus
Euphorbia,abyssiniaca
possibly G. Carinata,var. Verucosa
African Violet,Maui
Sansevieria trifasciata,Mother in law’s tongue
Plectranthus,'Mona Lavender'

The rules governing quoted strings and suchlike remain to be explored, but single quotes in the CSV file pass through unchanged.

Putting a tab at the point of the marker will prevent it from falling free when cut out, should you want to try raising the sheet above the platform to reduce the amount of crud accumulating on the back side.

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

Laser-cut Cutworm Collars

Mary, having had considerable trouble with cutworms in her gardens, routinely deploys cardboard collars around new plants:

Cutworm Collars - assembled
Cutworm Collars – assembled

It seems cutworms trundle around until they find an edible plant, chew through the stem and topple the plant, then trundle off without taking another bite. A small cardboard barrier prevents them from sensing the plant: apparently, motivation to climb a short wall hasn’t yet evolved.

Up to this point, Mary applied scissors to tissue boxes, but I proposed an alternative with an adjustable fit to any plant:

Laser Cutting Cutworm Collars
Laser Cutting Cutworm Collars

A splayed cardboard box rarely lays flat, a condition enforced by a few MDF stops used as clamps.

Come to find out no two tissue boxes have identical dimensions, even boxes from the same brand / retailer, so lay out duplicates of the collar template to match your stockpile.

That was fun!

The SVG image 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.

OMTech 60 W Laser: Adjustable Honeycomb Stops

When you (well, I) get fussy about angular alignment on the laser cutter’s honeycomb platform, an adjustable stop or two may come in handy:

Laser Honeycomb - Adjustable Pins
Laser Honeycomb – Adjustable Pins

That’s a serving suggestion based on a true story, because I really wasn’t all that fussy about precise engraving alignment on those signs.

A more typical situation on a smaller scale:

Laser Honeycomb - Adjustable Pins - engraving
Laser Honeycomb – Adjustable Pins – engraving

The scrap of MDF with three holes provides angular alignment for the little two-color acrylic test coupon, so you can tuck successive squares into the corner, hammer them with slightly different patterns, then compare the results.

The stops are an off-center hole (the ±3 text gives the offset) in an MDF disk with an acetal post:

Laser Honeycomb - Adjustable Pins - detail
Laser Honeycomb – Adjustable Pins – detail

The 3 mm SHCS provides a convenient way to turn the post and disk, so the threading isn’t critical. Sufficiently snug threading will let you turn the screw counterclockwise without loosening it, but that surely depends on how tightly the 8 mm section fits into the honeycomb. The larger top section is 9mm, cleaned up from the rod’s nominal 3/8 inch OD, for a jam fit into the 8.8 mm + 0.1 mm kerf hole.

The SVG images 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 ±5 mm offset disk may be more useful with larger items and now you know where those three holes came from.