Roadside Jewelry

I spotted a piece of jewelry during a recent walk:

Headlight Condenser - rear
Headlight Condenser – rear

The other side shows off The Shiny Bit:

Headlight Condenser - front
Headlight Condenser – front

I seem to have swapped the “front” and “rear” labels; the flat side faces the LED / HID bulb.

It looked even better after extraction and casual cleaning:

Headlight Condenser - sunlit
Headlight Condenser – sunlit

It seems someone with a relatively new car had a fairly high energy accident just north of Red Oaks Mill. The remainder of the debris consisted of shattered engineering plastic. We’ll never know the rest of the story.

Both lens surfaces have a slight nubbly finish, perhaps to produce some side light around the main beam. The rectangular opening apparently shaped the low beam and doesn’t appear movable, so perhaps the car had separate headlights for the high beams.

I’m not quite sure what to do with a chipped condenser lens, so it’s sitting on the windowsill (in a sun-safe orientation) along with many other glittery bits of glass I’ve collected over the years.

Pogo Pins

A Pogo Pin reference may be useful:

  • P.. and R.. refer to Pin and Receptacle (a.k.a. socket), respectively
  • Pxx  and Rxx = nominal pin diameter in 0.01 mm units: P50 = 0.48 mm

For pins, the suffix -hn indicates pin head shape, the most useful of which may be:

  • B1: 45° cone
  • J1: dome end
  • Dx: large dome, also 1D
  • Gx: cylinder
  • Ex: large 90° cone, sometimes 1E
  • T2 – large chisel

For sockets, the suffix -ntl gives:

  • n – entry shape: 1 = shaped entry, 2 = straight entry
  • t – termination: C = crimp, S = solder, W = wire
  • l – length of wire in 100 mm units: 7 = 700 mm

From what I can find on eBay, all pins have 6 mm travel with typically 75 / 100 / 180 g spring force.

A picture ripped from the reference to forestall link rot:

P75 Spring Test Probes
P75 Spring Test Probes

Memo to Self: US-based eBay sellers charge three times more than Chinese sellers, but deliver in one-third the time.

[Update: Simon sends a link to Everett Charles Technologies, a pogo-pin manufacturer providing “Probably much more information than anyone should ever want”. Of course, eBay / Amazon junk may not meet any particular specs, so scale your expectations accordingly.]

MPCNC: Plotter Pen Holder Spring Constant

Watching the MPCNC plot Spirograph patterns led me to wonder about how much force the printed drag knife holder applies to the pen:

Spirograph - liquid ink pen - detail
Spirograph – liquid ink pen – detail

The HP 7475A plotter spec calls for 19 g = 0.67 oz of downward force on the pen, so, in an ideal world, one might want to use one’s collection of aging plotter pens in a similar manner.

Plotter pen, meet digital scale:

MPCNC - Plotter pen force test
MPCNC – Plotter pen force test

Stepping the pen downward in 0.1 mm increments produced a set of numbers and a tidy linear fit graph:

MPCNC Plotter Pen Holder - Spring Constant
MPCNC Plotter Pen Holder – Spring Constant

I hereby swear I’m not making things up: the spring constant really is a nice, round 100 g/mm!

I set plot_z = -1.0 in the GCMC program, with Z=0.5 touched off atop a defunct ID card on the paper surface to compensate for any tabletop warp / bow / misalignment, plus any errors from the tool length probe. An eyeballometric scan against a straightedge shows pretty nearly no misalignment, which means the holder mashes the pen against the paper with about 100 g of force, five times the HP spec.

A distinct case of pen abuse rears its ugly head.

It’s time to conjure a height probe for the tool holder.

Spirograph Random Numbers: What Are The Odds?

The GCMC Spirograph Generator program chooses parameters using pseudo-random numbers based on a seed fed in from the Bash script, so I was surprised to see two plots overlap exactly:

Overlaid pattern - G-Code simulator
Overlaid pattern – G-Code simulator

The two overlapping traces are the 15 inward-pointing wedges around the central rosette.

The first one:

(PRNG seed: 38140045)
(Paper size: [16.50in,14in])
(PlotSize: [15.50in,13.00in])
(Stator 3: 150)
(Rotor  4: 40)
(GCD: 10)
(Offset: -0.94)
(Dia ratio: -0.27)
(Lobes: 15)
(Turns: 4)
(Plot scale: [5.11in,4.29in])
(Tool change: 1)

The second one:

(PRNG seed: 74359295)
(Paper size: [16.50in,14in])
(PlotSize: [15.50in,13.00in])
(Stator 3: 150)
(Rotor  4: 40)
(GCD: 10)
(Offset: -0.93)
(Dia ratio: -0.27)
(Lobes: 15)
(Turns: 4)
(Plot scale: [5.12in,4.30in])
(Tool change: 3)

The Offset isn’t quite the same, but the pen width covers up the difference.

With only four Stators and 17 Rotors, the probability of picking the same pair works out to 0.25 × 0.059 = 1.4%. You can sometimes get the same number of Lobes and Turns from several different Stator + Rotor combinations, but these were exact matchs with the same indices.

The Pen Offset within the Rotor comes from a fraction computed with ten bit resolution, so each Offset value represents slightly under 0.1% of the choices. If any four adjacent values look about the same, then it’s only eight bits of resolution and each represents 0.4%.

The Rotor and Stator set the Diameter ratio, but the sign comes from what’s basically a coin flip based on the sign of a fraction drawn from 256 possibilities; call it 50%.

Overall, you’re looking at a probability of 28 ppm = 0.0028%, so I (uh, probably) won’t see another overlay for a while …

I don’t know how to factor the PRNG sequence into those numbers, although it surely affects the probability. In this case, two different seeds produced nearly the same sequence of output values, within the resolution of my hack-job calculations.

Whatever. It’s good enough for my simple purposes!

MPCNC: Spirograph Generator with Tool Changes

An improved version of my GCMC Spirograph pattern generator, now with better annotation and tool changes:

Spirograph pattern - overview
Spirograph pattern – overview

The GCMC code sets the stator and rotor gear tooth counts, the rotor diameter, and the pen offset using a pseudo-random number generator. This requires randomizing the PRNG seed, which I do in the calling script with the nanosecond of the current second: rnd=$(date +%N).

The G-Code file name also comes from the timestamp:

ts=$(date +%Y%m%d-%H%M%S)
# blank line to make the underscore visible

Which means you must call the Bash script slowly to generate a pile o’ plots:

for i in {1..60} ; do sh /mnt/bulkdata/Project\ Files/Mostly\ Printed\ CNC/Patterns/ ; sleep 1 ; done

Sift through the heap with drag-n-drop action using an online G-Code previewer. There seems no clean way to convert G-Code to a bitmap on the command line, although you can do it manually, of course.

The GCMC program spits out the G-code for one plot at a time, so the Bash script calls it four times to fill a sheet of paper with random patterns:

for p in $(seq 4)
  rnd=$(date +%N)
  gcmc -D Pen=$p -D $Paper -D PRNG_Seed=$rnd $Flags $LibPath -q "$Spirograph" >> $fn

The -q parameter tells GCMC to not include the prolog and epilog files, because the calling script glues those onto the lump of G-Code for all four plots.

The -D Pen=$p parameter tells the GCMC program which “tool” to select with a Tn M6 tool change command before starting the plot. Although plotter pens have a well-defined position in the holder and a pretty nearly constant length, you must have a tool length probe installed and configured:

MPCNC Tool Length Probe - Plotter Pen
MPCNC Tool Length Probe – Plotter Pen

Set the overall sheet size in inches or millimeters to get a plot centered in the middle of the page with half-inch margins all around:


With all that in hand, those good old black ceramic-tip pens give impeccable results:

Spirograph pattern - black ceramic pen - detail
Spirograph pattern – black ceramic pen – detail

The surviving ones, anyhow. I must apply my collection of Sakura Micron pens to this task.

The other three colors come from fiber pens with reasonably good tips:

Spirograph pattern - central details
Spirograph pattern – central details

They’re a lot like diatoms: all different and all alike.

The GCMC and Bash source code as a GitHub Gist:

# Spirograph G-Code Generator
# Ed Nisley KE4ZNU - December 2017
Flags="-P 2"
LibPath="-I /opt/gcmc/library"
Spirograph='/mnt/bulkdata/Project Files/Mostly Printed CNC/Patterns/Spirograph.gcmc'
ts=$(date +%Y%m%d-%H%M%S)
echo Output: $fn
rm -f $fn
echo "(File: "$fn")" > $fn
cat $Prolog >> $fn
for p in $(seq 4)
rnd=$(date +%N)
gcmc -D Pen=$p -D $Paper -D PRNG_Seed=$rnd $Flags $LibPath -q "$Spirograph" >> $fn
cat $Epilog >> $fn
view raw hosted with ❤ by GitHub
// Spirograph simulator for MPCNC used as plotter
// Ed Nisley KE4ZNU - 2017-12-23
// Spirograph equations:
// Loosely based on GCMC cycloids.gcmc demo:
// Required command line parameters:
// -D Pen=n pen selection for tool change and legend position
// -D PaperSize=[x,y] overall sheet size: [17in,11in]
// -D PRNG_Seed=i non-zero random number seed
// Greatest Common Divisor
// Inputs = integers without units
function gcd(a,b) {
local d=0;
if (!isnone(a) || isfloat(a) || !isnone(b) || isfloat(b)) {
warning("Values must be dimensionless integers");
while (!((a | b) & 1)) { // remove and tally common factors of two
a >>= 1;
b >>= 1;
while (a != b) {
if (!(a & 1)) {a >>= 1;} // discard non-common factor of 2
elif (!(b & 1)) {b >>= 1;} // ... likewise
elif (a > b) {a = (a - b) >> 1;} // gcd(a,b) also divides a-b
else {b = (b - a) >> 1;} // ... likewise
local GCD = a*(1 << d); // form gcd
// message("GCD: ",GCD);
return GCD;
// Max and min functions
function max(x,y) {
return (x > y) ? x : y;
function min(x,y) {
return (x < y) ? x : y;
// Pseudo-random number generator
// Based on xorshift:
// Requires initial state from calling script
// -D "PRNG_Seed=$(date +%N)"
function XORshift() {
local x = PRNG_State;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
PRNG_State = x;
return x;
// Spirograph tooth counts mooched from:
// Stators has both inside and outside counts, because we're not fussy
Stators = [96, 105, 144, 150];
Rotors = [24, 30, 32, 36, 40, 45, 48, 50, 52, 56, 60, 63, 64, 72, 75, 80, 84];
// Start drawing things
// Set these variables from command line
comment("PRNG seed: ",PRNG_Seed);
PRNG_State = PRNG_Seed;
// Define some useful constants
AngleStep = 2.0deg;
Margins = [0.5in,0.5in] * 2;
comment("Paper size: ",PaperSize);
PlotSize = PaperSize - Margins;
comment("PlotSize: ",PlotSize);
// Set up gearing
s = (XORshift() & 0xffff) % count(Stators);
StatorTeeth = Stators[s];
comment("Stator ",s,": ",StatorTeeth);
r = (XORshift() & 0xffff) % count(Rotors);
RotorTeeth = Rotors[r];
comment("Rotor ",r,": ",RotorTeeth);
GCD = gcd(StatorTeeth,RotorTeeth); // reduce teeth to ratio of least integers
comment("GCD: ",GCD);
StatorN = StatorTeeth / GCD;
RotorM = RotorTeeth / GCD;
L = to_float((XORshift() & 0x3ff) - 512) / 100.0; // normalized pen offset in rotor
comment("Offset: ", L);
sgn = sign((XORshift() & 0xff) - 128);
K = sgn*to_float(RotorM) / to_float(StatorN); // normalized rotor dia
comment("Dia ratio: ",K);
Lobes = StatorN; // having removed all common factors
Turns = RotorM;
comment("Lobes: ", Lobes);
comment("Turns: ", Turns);
// Crank out a list of points in normalized coordinates
Path = {};
Xmax = 0.0;
Xmin = 0.0;
Ymax = 0.0;
Ymin = 0.0;
for (a=0.0deg ; a <= Turns*360deg ; a += AngleStep) {
x = (1 - K)*cos(a) + L*K*cos(a*(1 - K)/K);
if (x > Xmax) {Xmax = x;}
elif (x < Xmin) {Xmin = x;}
y = (1 - K)*sin(a) - L*K*sin(a*(1 - K)/K);
if (y > Ymax) {Ymax = y;}
elif (y < Ymin) {Ymin = y;}
Path += {[x,y]};
//message("Max X: ", Xmax, " Y: ", Ymax);
//message("Min X: ", Xmin, " Y: ", Ymin); // min will always be negative
Xmax = max(Xmax,-Xmin); // odd lobes can cause min != max
Ymax = max(Ymax,-Ymin); // ... need really truly absolute maximum
// Scale points to actual plot size
PlotScale = [PlotSize.x / (2*Xmax), PlotSize.y / (2*Ymax)];
comment("Plot scale: ", PlotScale);
Points = scale(Path,PlotScale); // fill page, origin at center
// Set up pen
if (Pen > 0) {
comment("Tool change: ",Pen);
// Plot the curve
safe_z = 1.0mm;
plot_z = -1.0mm;
tracepath(Points, plot_z);
// Put legend in proper location
TextSize = [3.0mm,3.0mm];
TextLeading = 1.5; // line spacing as multiple of nominal text height
MaxPen = 4;
line1 = typeset("Seed: " + PRNG_Seed + " Stator: " + StatorTeeth + " Rotor: " + RotorTeeth,FONT_HSANS_1);
line2 = typeset("Offset: " + L + " GCD: " + GCD + " Lobes: " + Lobes + " Turns: " + Turns,FONT_HSANS_1);
maxlength = TextSize.x * max(line1[-1].x,line2[-1].x);
textpath = line1 + (line2 - [-, TextLeading, -]); // undef - n -> undef to preserve coordinates
if (Pen == 1 || Pen > MaxPen ) { // catch and fix obviously bogus pen selections
textorg = [PlotSize.x/2 - maxlength,-(PlotSize.y/2 - TextLeading*TextSize.y)];
elif (Pen == 2) {
textorg = [-PlotSize.x/2,-(PlotSize.y/2 - TextLeading*TextSize.y)];
elif (Pen == 3) {
textorg = [PlotSize.x/2 - maxlength, PlotSize.y/2 - TextSize.y];
elif (Pen == 4) {
textorg = [-PlotSize.x/2, PlotSize.y/2 - TextSize.y];
else {
Pen = 0; // squelch truly bogus pens
textorg = [0mm,0mm]; // just to define it
if (Pen) { // Pen = 0 suppresses legend
placepath = scale(textpath,TextSize) + textorg;
comment("Legend begins");
if (Pen == 1) { // add attribution along right margin
attrpath = typeset("Ed Nisley - KE4ZNU -",FONT_HSANS_1);
attrpath = rotate_xy(attrpath,90deg);
attrorg = [PlotSize.x/2,5*TextLeading*TextSize.y - PlotSize.y/2];
placepath = scale(attrpath,TextSize) + attrorg;
comment("Attribution begins");
view raw Spirograph.gcmc hosted with ❤ by GitHub

A Spirograph for Christmas

Gotta play with my new toy:

Spirograph - liquid ink - ceramic tip
Spirograph – liquid ink – ceramic tip

That’s with a set of liquid ink and ceramic tip plotter pens. They’re unbelievably cranky, but produce wonderfully fine lines:

Spirograph - liquid ink pen - detail
Spirograph – liquid ink pen – detail

Text comes out exactly the way vector lettering should look:

Spirograph - liquid ink pen text - detail
Spirograph – liquid ink pen text – detail

There’s a slight shake visible at 500 mm/min = 8.3 mm/s, but it’s Good Enough.

All the pen-and-ink traffic around the center produced a ring of damp green fuzz:

Spirograph - liquid ink - ceramic tip - center detail
Spirograph – liquid ink – ceramic tip – center detail

The artsy part of the plot ran at 1800 mm/min = 30 mm/s, with little of the wobbulation at 6000 mm/min = 100 mm/s. None of that would matter with a router, of course.

It’s a nice, Christmasy design in kinda-red and sorta-green.

From the stack of plots accumulating near the MPCNC bench:

This slideshow requires JavaScript.

Plots 7 and 9 show the tape sutures required to produce a 26×18 inch sheet covering the MPCNC’s full work area. The squat plots fit on B-size sheets and the rest come from 17×14 inch artist’s sketchpad sheets.

I used Google PhotoScan to capture and rectangularize paper sheets from the floor or atop the bench, then battered the contrast and crushed the file size with a one-liner:

i=1 ; for f in 1* ; do printf -v dn "Spiro %02d.jpg" $(( i++ )) ; convert $f -level '10,80%' -density 300 -define jpeg:extent=300KB tweaked/"$dn" ; done

The plots look great in person (modulo some incremental software improvements), but the slideshow images look awful because:

  • Google PhotoScan produces surprisingly low-res images
  • I’m overly compressing the results

They’re not (yet) art and there’s no point in a high-quality workflow.

Enjoy the day …