Last active
December 24, 2017 14:57
-
-
Save ednisley/6ee7f883860cdd13f3b2ebe1950942ec to your computer and use it in GitHub Desktop.
GCMC source code: Spirograph Generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
More details on my blog at https://wp.me/poZKh-7ff