Skip to content

Instantly share code, notes, and snippets.

@ednisley
Last active December 24, 2017 14:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ednisley/6ee7f883860cdd13f3b2ebe1950942ec to your computer and use it in GitHub Desktop.
Save ednisley/6ee7f883860cdd13f3b2ebe1950942ec to your computer and use it in GitHub Desktop.
GCMC source code: Spirograph Generator
# Spirograph G-Code Generator
# Ed Nisley KE4ZNU - December 2017
Paper='PaperSize=[16.5in,14in]'
Flags="-P 2"
LibPath="-I /opt/gcmc/library"
Spirograph='/mnt/bulkdata/Project Files/Mostly Printed CNC/Patterns/Spirograph.gcmc'
Prolog="/home/ed/.config/gcmc/prolog.gcmc"
Epilog="/home/ed/.config/gcmc/epilog.gcmc"
ts=$(date +%Y%m%d-%H%M%S)
fn='Spiro_'${ts}'.ngc'
echo Output: $fn
rm -f $fn
echo "(File: "$fn")" > $fn
cat $Prolog >> $fn
for p in $(seq 4)
do
rnd=$(date +%N)
gcmc -D Pen=$p -D $Paper -D PRNG_Seed=$rnd $Flags $LibPath -q "$Spirograph" >> $fn
done
cat $Epilog >> $fn
// Spirograph simulator for MPCNC used as plotter
// Ed Nisley KE4ZNU - 2017-12-23
// Spirograph equations:
// https://en.wikipedia.org/wiki/Spirograph
// Loosely based on GCMC cycloids.gcmc demo:
// https://gitlab.com/gcmc/gcmc/tree/master/example/cycloids.gcmc
// 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
include("tracepath.inc.gcmc");
include("engrave.inc.gcmc");
//-----
// Greatest Common Divisor
// https://en.wikipedia.org/wiki/Greatest_common_divisor#Using_Euclid's_algorithm
// 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;
d++;
}
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:
// https://en.wikipedia.org/wiki/Xorshift
// www.jstatsoft.org/v08/i14/paper
// 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:
// http://nathanfriend.io/inspirograph/
// 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);
toolchange(Pen);
}
//-----
// Plot the curve
feedrate(3000.0mm);
safe_z = 1.0mm;
plot_z = -1.0mm;
tracepath(Points, plot_z);
//-----
// Put legend in proper location
feedrate(500mm);
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");
engrave(placepath,safe_z,plot_z);
}
if (Pen == 1) { // add attribution along right margin
attrpath = typeset("Ed Nisley - KE4ZNU - softsolder.com",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");
engrave(placepath,safe_z,plot_z);
}
goto([-,-,25.0mm]);
@ednisley
Copy link
Author

More details on my blog at https://wp.me/poZKh-7ff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment