The Spirograph Pyrograph
One of my favourite toys as a kid was my Spirographtm. In recent years my 3D printer has been a favourite toy. So when Covid-19 lockdown struck, I had an idea to combine these with a lifelong obsession with programming (I wrote my first BASIC program in 1971); these things coalesced into a project.
Draw hypocycloid (Spirograph(tm)) patterns using Python and save the best as GCODE for a 3D printer fitted with a pyrograph laser to make coasters.
Supplies
- A computer with Python installed and set up.
- I used a Windows 10 desktop PC
- Eclipse IDE with Python extension (PyDev) [see here]
- Other Python IDEs will probably work fine.
- WxPython toolkit
- A 3D printer
- Mine is a Creality Ender 3 pro
- A laser module (~500mW)
- Make sure you get one compatible with your printer
- Mine says "d-b500f" from Creality
- Check you are happy opening the printer control box and changing plugs on the motherboard if necessary
- Safety glasses or goggles for the wavelength (colour) of your laser
- A Supply of coaster blanks
- Masking (painter's) tape - I use the nice green (widely available) Frog Tape
- Scrap card for protecting your build plate and setting up. I use the grey reasonably dense card from the back of A4 writing pads
- For finishing the coasters
- some varnish of your choice (I use clear matt)
- some squares of thin felt and a suitable glue
CNC Pyrograph
Safety First
Laser goggles or protective glasses are a must! Get a few pairs if you intend to invite your friends around to admire the machine in action. If the machine is on with the laser connected, you should be wearing your laser glasses.
Installation
Brief overview
Setting your 3D printer up with a laser engraver module is a well trodden path, so I will not be going into much detail. There are many descriptions and videos on the web (many of them machine and laser specific). I bought the laser from the printer manufacturer (Creality) to avoid incompatibility problems.
In general the laser is controlled by the pulse width modulated output that usually controls the fan speed. Many lasers sold for this have the correct plug to simply unplug the fan control and swop in the laser control wire. Take care routing the cable to the hot head mounting plate. My laser attachment is straightforward: it attaches to the front of the extruder with a set of strong magnets. A spirit level is useful to get it as close to pointing straight down as possible.
Setting the laser parameters
In order to make coasters you need to know where on the build plate the laser is pointing.
Different lasers have different attachment methods, so you will need to do some experiments with your set up to determine origin of the pyrography drawing area.
- Measure as accurately as reasonable the offset between the laser lens and the extruder nozzle
- Put on your safety glasses
- Place some protective card on the build plate
- I put the Creality flexible magnetic build surface on to protect the base plate and put a whole A4 card on top of this.
- Turn on the printer
- the laser should be off.
- Home the x and y positions setting the z to the focal length of the laser plus the thickness of the card
- Using your offset measurements, move the head so that the laser is at the origin of the build plate and move it in10mm x and y
- You have still got your safety specs on, haven't you?
- On the printer's panel locate the fan speed control
- turn the laser (fan control) on at 5%.
- You should see the flickering (pulse width modulated) laser spot; if not, slowly increase the power until you do. On the Ender 3, you have to click the control knob to register a new setting; it is not a "live" control
- keep moving the protective card so as not to burn through it.
- Adjust either the z-height (fixed focal length) or the focus knob to get as small a spot as possible
- Turn off the laser (fan setting to 0).
- Check the z-height, subtract the thickness of the card and record this as the focus height.
- Check the spot is about 10mm in from the build plate edges (adjust if necessary) and record the x and y settings as the origin for coaster placement.
Gcode is the most widely used Computer Numerical Control (CNC) language (it is the language used by slicers (such as Cura) to send instructions to printers); it is capable of highly complex control of such as milling machines, we will use a small part of the language to control the Spirograph Pyrograph, so we only need to understand a small number of Gcode controls codes here (see Wikipedia for a fuller list). The gcode files attached are heavily commented to introduce the control codes in this project.
Setting the gcode parameters is a process of trial and adjustment
- Download the param.gcode file from the "supporting files"
- Open it in a text editor (I use Notepad++)
- This code is designed to burn a series of lines with different parameters
- It uses gcode variables which are set to the parameters you found above.
- Replace the values for my printer with those for your printer and save the file.
- Save the file to a micro SD card (or whatever you need for your printer) as Octo Print doesn't seem to like home brew gcode.
- Safety glasses on.
- Place a card sheet on the print mat
- Upload your updated param.gcode to your printer. Load and run it (the actual operation will be printer specific). For the Ender 3 pro, insert the micro SD card and run the code from there.
- You should have a set of lines (similar to the ones in the photo) of decreasing density and increasing spottiness (you may meet to use a magnifying glass to see this).
- Examine these lines, either choose one that looks good, or refine the code and repeat the process.
You should now have all the parameters needed for the pyrography (or "engraving").
Drawing the axes
Rather than relying on our measurements to place the coaster blank, we will get the laser to draw the axes for us, not actually on the base plate but on some tape stuck on the base plate; I use green Frog Tape because I like the colour, any masking/painters tape will do.
- Using the origin you recorded above, place a double layer of masking tape so that the axes will run up the middle of the tape lines.
- Open the axes.gcode file (attached) in your text editor.
- Replace the parameters for my printer with those for your printer.
- Save the file.
- Safety glasses on
- Transfer the gcode file to your printer and run it.
You should now have the axes drawn on the base plate ready to start printing (see photo).
Hypocycloids
There are several equivalent formulations of the hypocycloid equations!
The equation I use here to generate the patterns is shown as eq1; for programming in Python this becomes:
x = ((r_static-r_rotate) * math.cos((r_rotate/r_static)*t)) + p*math.cos((t*((1-r_rotate/r_static))))
y = ((r_static-r_rotate) * math.sin((r_rotate/r_static)*t)) - p*math.sin((t*((1-r_rotate/r_static))))
r_static and r_rotate refer to the radii of the fixed and the rotating wheels of the Spirograph, p is the pen position (its distance from the centre of the rotating wheel.
This is put in a for loop to step through a range of values of t and plotting each line segment to join the new {x, y} with the previous on the fly, using the wxpython library function dc.Drawline. Examine the full code for more details (such as where the first {x1, y1} comes from
for n in range(1, steps+1):
t = n* step
x = (((r_static-r_rotate) * math.cos((r_rotate/r_static)*t)) +
p*math.cos((t*((1-r_rotate/r_static)))))
x2 = int((scale * x) + (xCenter + .5))
y = (((r_static-r_rotate) * math.sin((r_rotate/r_static)*t)) -
p*math.sin((t*((1-r_rotate/r_static)))))
y2 = int((scale * y) + (yCenter + .5))
dc.DrawLine(x1, y1, x2, y2)
x1 = x2
y1 = y2
Customising the code
The code used in this Instructable is attached as drawSpiro_Instbl_b.py
The code is fairly straightforward, and hopefully sufficiently documented to be self explanatory. There is no GUI and the code runs once, producing either a graphic on screen, or a graphic, a jpeg file and a gcode file, controlled by the "do_print" boolean variable. The expected use is to keep the file open in the Integrated Development Environment (IDE), set the ring radii, pen position(s) and the number of drawing steps (equivalent to the resolution of the curve drawing), run the program with do_print False, if you like the pattern, set do_print to True and re-run without changing the other parameters.
- Open the code in your preferred IDE (I will refer to Eclipse with Pydev installed).
- (I have run the code in IDLE and it runs okay')
- Change the path "my_directory" (line 39) to the path where you would like the output files saved. For simplicity in the code, this directory must exist!
- Locate the parameters for the laser pyrography (prefixed "burn_" in the global variables) and change them to match your printer.
- If you want to use anything other than laser power set to 255 and the transport speed set to 200, you will need to use "Find/Replace" from the Edit menu to alter these values.
- Run the program
- First time select Run -> Run As -> 1 Python Run
- For subsequent runs, the Run icon on the top bar should work okay.
- Change the radii and the pen positions to change the pattern
- You can, of course, just change the parameters until you find something you like; for those of a more controlled bent, the number of nodes in the pattern is the LCM of the radii divided by the radius of the rotating ring.
- LCM(100, 25) = 100; 100/25 = 5
- Try r_static = 175, r_rotate = 50; LCM(135, 50) = 350; 350/50 = 7
- The pen positions are a Python list of numbers (I generally stick with integers, but there is no restriction!)
- Depending on symmetry, sometimes negative pen positions give interesting results.
Downloads
Writing GCODE
When do-print is TRUE, the program writes a gcode file. This is done in four parts.
- There is a comments section, generated by GcodePreamble(), which you might want to edit following the format example.
- Then some set up code (GcodeSetup()), the 4th line uses a Python f-string to calculate the Z height to set the laser position from burn_z and burn_thick which you should set in the Global Variables section (as I said, I started programming in the '70s when these were officially a Good Idea).
- The bulk of the gcode is written on the fly by lines 236-241 and 258-261. The program checks if gcode is needed, if so,
- for each pen position, 236 -241 send generates a <G0 Xb_x Yb_y> line using a Python f'-string to format the values b_x and b_y as a 6 character string with 2 decimal places to move the head to the start position and an M106 S255 to turn the laser on full, writing them directly to the gcode file.
- each new point generated a G0 line to move the head to leaving the laser on. 262-264 turn the laser off between pen lines.
- Finally GcodeTail() turns the laser off (should be redundant but better safe ...) and writes any ending comments.
Putting It All Together
Once the printer is set up:
- Open the Python program in you IDE.
- Set the ring sizes and pen positions.
- Check do_print is false.
- Run the code.
- If you like the picture in the window
- Set do_print to True
- Run the program again
- (this will generate output files).
- Set do_print to false.
- Delete the drawing window (or your screen will quickly fill up with them).
- Repeat until you have a few gcode files to burn.
- Copy the gcode files to the printer (I use a microSD card).
- Put your safety glasses on.
- Place a coaster blank on the axes on the print plate.
- Select the first file and print it.
- Print the remaining files.
You can get some complicated patterns by leaving the coaster in place and printing two or more patterns together (try it on some scrap card to see if they work together).
I usually finish the coasters with a couple of coats of clear matt varnish and glue a square of thin felt to the base (you can also use this to cover any failed prints!).
Finally
This is not a closed end project and will hopefully inspire others to develop new ideas. More than two wheels, non-circular paths, and patterns generated from other figures (I am looking at Lissajous curves). Drawing with a plotter rather than burning with a laser would allow colours to be used.
Finally, finally the code is not perfect and the scaling can sometimes be slightly out. Please use the comments to suggest improvements.