40-year-old toy meets modern scripting language

As a kid, one of my favorite toys was my Spirograph Set. I had the original 1960s version, as pictured in that link. I still remember being completely blown away by how some plastic gears and colored ballpoint pens could produce a near-infinite variety of intricate geometric patterns. Yeah, I was already a bit of a geek even when I was 7 years old!

Somewhere along my meandering journey from childhood to middle age, my Spirograph set was lost. But a recent query from my wife (who has become interested in geometric designs for art quilts) rekindled my interest in this long-lost toy. I haven’t gone and bought an old Spirograph set on eBay (yet…), but I’ve done the next best (in some ways better!) thing.

With a bit of research on Wikipedia and Wolfram MathWorld, I’ve learned that Spirograph produces mathematical curves called hypotrochoids and epitrochoids. In a nutshell, if you take a fixed circle, roll another circle around its inner or outer circumference, and affix a pen at some distance from the rolling circle’s center, the pen traces a hypotrochoid (if the rolling circle is inside the fixed one) or an epitrochoid (if the rolling circle is outside the fixed one). The number of lobes in the pattern depends on the ratio of the sizes of the fixed and rolling circles, and the shape of the lobes (how pointy or rounded they are) is determined by both the ratio of the sizes of the circles and the distance of the pen from the moving circle’s center.

It’s a fairly simple matter to simulate arbitrary Spirograph patterns on a PC, and display the results in a window. I’ve written a simple Python script to do this—I call it Pyrograph, and you can grab the (admittedly rather primitive) v0.1 release of it here (right-click the link, and save the file locally). Update: There’s a v0.2 release here, which fixes the issue when executing from within the Python Idle environment that someone reported in the comments. It has also been noted that the Windows-style line endings in the file can cause problems for Linux users ("bad interpreter" errors); Linux users should convert the line endings using the dos2unix command before attempting to run the script.

To play with the script yourself, you’ll need to install the Python interpreter on your system. Python is a freely available, Open Source, cross-platform scripting language that has a huge number of add-on packages available for various application areas. For Windows, simply download and run the installer for the latest 2.x release, which should be available from this page. If you’re a Tux fan, install the base Python package and the "tkinter" add-on from your Linux distro’s software repository (many distros install the Python interpreter by default, so you may need to install only tkinter).

Take a look at the Pyrograph script in your favorite plain-text editor (even Notepad will do in a pinch). The Pyro method is the heart of the script; the first parameter is the radius of the fixed circle, the second parameter is the radius of the rolling circle, the third parameter is the offset of the pen from the center of the rolling circle, the fourth parameter rotates the entire pattern a specified number of degrees, the fifth parameter scales the entire pattern up or down in size (values > 1 enlarge it, values < 1 shrink it), and the sixth parameter specifies the color of the pen.

The Draw method is invoked when the script is run; it calls the Pyro method one or more times. To change the pattern, edit the Draw method. To see the pattern, just run the script (type its name in a command prompt window, or double-click on the file).

For each of the following examples, I’ve listed the Draw method, immediately followed by a screen capture of the pattern it produces. (Other than the Draw method, the rest of the script remains unchanged.)

    # Example #1: Simple hypotrochoid pattern, with the rolling circle fairly large and the pen near its edge

    def Draw(self):

        self.Pyro(300, 255, 220, 0, 1, "#0000c0")

Pyrograph #1

    # Example #2: Same as previous, but with the pen moved in towards the center of the rolling circle

    def Draw(self):

        self.Pyro(300, 255, 80, 0, 1, "#0000c0")

Pyrograph #2

    # Example #3: Hypotrochoid with a smaller radius rolling circle

    def Draw(self):

        self.Pyro(300, 45, 35, 0, 1, "#0000c0")

Pyrograph #3

    # Example #4: Multiple overlaid hypotrochoids (different pen offset for each)

    def Draw(self):

        for i in range(0, 15):

            self.Pyro(300, 200, 50 + i * 10, 0, 1, "#0000c0")

Pyrograph #4

    # Example #5: Same as previous, but with each iteration rotated by a multiple of 5 degrees

    def Draw(self):

        for i in range(0, 15):

            self.Pyro(300, 200, 50 + i * 10, i * 5, 1, "#0000c0")

Pyrograph #5

    # Example #6: A simple epitrochoid

    def Draw(self):

        self.Pyro(150, -60, 50, 0, 1, "#0000c0")

Pyrograph #6

    # Example #7: Some multi-colored epitrochoids

    def Draw(self):

        for i in range(0, 24, 4):

            self.Pyro(150, -60, 50, i, 1, "#c00000")

            self.Pyro(150, -60, 50, i + 24, 1, "#00c000")

            self.Pyro(150, -60, 50, i + 48, 1, "#0000c0")

Pyrograph #7

Well, I hope that’s piqued your interest. Have fun!

Comments closed
    • gerryg
    • 12 years ago

    Python is slowly becoming the defacto scripting language (non-shell language, that is) for Unix/Linux systems, and it’s quite popular on Windows now, too. It’s a good general-purpose language, much like Java, but a little easier to work with I think, for smaller projects. I’m waiting for a gift certificate I’m expecting in the mail, but then I planned to buy ยง[<http://www.amazon.com/exec/obidos/tg/detail/-/0470068221<]ยง "Game Programming: The L Line, The Express Line to Learning", which is based on Python. I plan to teach my son programming this summer, and Python looks like a reasonable starting langauge where he can be productive enough to actually get stuff completed and keep his interest up. Great post, I love seeing more variety on TR. BTW, you should check out the "GeekDad" column on Wired online (http://blog.wired.com/geekdad/), and also Make magazine (http://www.makezine.com/).

    • eloj
    • 12 years ago

    The ‘cover’ image looked like it could be from this demo.


      • just brew it!
      • 12 years ago

      Wow, that’s pretty slick! The description says he did it by hooking his oscilloscope up to his soundcard, and playing an audio file he created with a custom C program.

    • floodo1
    • 12 years ago

    how do i end the process? i ran it from inside IDLE and it says python shell is still running the first process ๐Ÿ™

      • just brew it!
      • 12 years ago

      Close the new Python Shell window that opened when you told Idle to run the script.

      I’m not a big fan of the Idle environment; it tends to interact poorly with applications which use tkinter (as you’ve just discovered). I generally just run the scripts from a Command Prompt window (or double-click them in Windows Explorer).

        • just brew it!
        • 12 years ago

        A quick follow-up for anyone else trying to use the Python Idle environment to run the Pyrograph script (instead of running the script directly). You can avoid the squirrelly behavior of the Close button by commenting out the last line of the script (the *[

    • ssidbroadcast
    • 12 years ago

    What if you’re an OS X fan? Do you have a version for that?

      • just brew it!
      • 12 years ago

      The script should be portable to any system that supports Python and the tkinter add-on. It looks like the Python download page has an OS X installer, so it is probably supported. But I haven’t tested it on OS X, since I don’t have easy access to an OS X machine.

        • ssidbroadcast
        • 12 years ago

        Is their any way I can port it to Processing?

      • cwg_at_opc
      • 12 years ago

      Ah, Mac OSX. you can run this in nodebox as is, or you can
      use the following(i’ve modified the linked code to run in nodebox)
      i’ve commented out all the lines which don’t work in nodebox
      directly – nodebox provides a nice IDE complete with programatic
      graphical output:

      # pyrograph.py – simple Python “Spirograph” demo
      # Copyright (C) 2008 Michael Uchima
      # History:
      # v0.1 – 29Apr08 – Initial release
      # v0.2 – 01May08 – Make it work properly whether it is executed directly or via Idle

      #from Tkinter import *
      from math import *

      # Window size, pattern resolution, etc.
      #windowSize = 600 # size of the window (both height and width)
      resolution = 360 # number of points to plot for each revolution
      pi = 3.14159265359 # mmm… pi

      size(800, 800)

      # Utility function to compute greatest common denominator
      def gcd(x, y):
      if x % y == 0:
      return y
      return gcd(y, x % y)

      # Our main window object
      #class MainWindow:

      #def __init__(self):
      # self.canvas = Canvas(self, width=windowSize, height=windowSize, bg=”#ffffff”,
      # cursor=”crosshair”, borderwidth=0)
      # self.canvas.grid(row=0, column=0)
      # self.canvas.xview_moveto(0)
      # self.canvas.yview_moveto(0)
      # self.Draw()

      # Method to draw an arbitrary “Spirograph” pattern
      # fixedRadius is the radius of the circle around which the moving wheel rotates
      # movingRadius is the radius of the moving wheel; positive puts it is inside the fixed wheel, negative puts it outside
      # penOffset is the offset of the pen from the center of the moving wheel
      # rotationOffset is the rotation in degrees of the overall pattern; positive is counter-clockwise
      # scaleFactor scales the pattern up or down without changing its shape
      # penColor is the color to use for drawing, as 6 hexadecimal digits (#rrggbb)
      #def Pyro(self, fixedRadius, movingRadius, penOffset, rotationOffset, scaleFactor, penColor):
      def Pyro( fixedRadius, movingRadius, penOffset, rotationOffset, scaleFactor):

      # check for impossible values
      if fixedRadius == 0 or movingRadius == 0:
      # log what we’re doing to the console window
      print “fixed=” + str(fixedRadius) + “, moving=” + str(movingRadius) \
      + “, offset=” + str(penOffset) + “, rotation=” + str(rotationOffset) \
      + “, scale=” + str(scaleFactor)# + “, color=” + str(mycolor)
      # flip moving wheel radius around so that positive values are inside
      movingRadius = -movingRadius
      # figure out how many rotations we need, and how many total points to compute
      turns = abs(movingRadius) / gcd(fixedRadius, abs(movingRadius))
      points = turns * resolution
      print “total number of points=”, points
      # scale the rotation value to be in radians
      rotate = rotationOffset * pi * 2 / 360.0
      # build list of points


      pointList = []
      for i in range(0, points + 1):
      #print “at step”, i
      t1 = i * 2 * pi / resolution
      t2 = t1 * (fixedRadius + movingRadius) / movingRadius
      x = (fixedRadius + movingRadius) * sin(t1) + penOffset * sin(t2)
      y = -(fixedRadius + movingRadius) * cos(t1) – penOffset * cos(t2)
      x1 = floor((x * cos(rotate) + y * sin(rotate)) * scaleFactor + WIDTH / 2 + 0.5)
      y1 = floor((-x * sin(rotate) + y * cos(rotate)) * scaleFactor + WIDTH / 2 + 0.5)
      if ( i == 0):
      savex = x1
      savey = y1

      line(savex, savey, x1, y1)
      savex = x1
      savey = y1

      #p = endpath()
      # draw the pattern
      #self.canvas.create_line(pointList, fill = penColor)

      #px = int(pointList[0])
      #py = int(pointList[0,1])
      #print “px=”, px, “py=”, py
      #beginpath( px, py )
      #print len(pointList)
      #print pointList[1, 1]
      #for j in range( 1, len(pointList) ):
      #print pointList[j,1]
      #lineto(int(pointList[j,1]), int(pointList[j,2]))

      # Draw the contents of the window – edit this method to change the pattern!
      # Example #1: Simple hypotrochoid pattern, with the rolling circle fairly large and the pen near its edge
      #def Draw(self):
      # self.Pyro(300, 255, 220, 0, 1, “#0000c0”)

      # Our main application object
      #class Application(Frame, MainWindow):
      # def __init__(self, master=None):
      # Frame.__init__(self, master)
      # MainWindow.__init__(self)
      # self.grid()

      # Create application object and go
      #app = Application()
      #app.master.title(“Pyrograph v0.2”)
      # This check for Idle works in Python 2.5.2; haven’t tested it in other versions
      #if not ‘idlelib’ in dir():
      # app.mainloop() # start tkinter main loop only if we weren’t run from Idle environment

      #fixedRadius, movingRadius, penOffset, rotationOffset
      #color( 0.0, 0.5, 1.0)
      stroke(0.2, 0.3, 0.5)
      Pyro(425, 575, 150, 15, .75)

        • just brew it!
        • 12 years ago

        Looks like the comments system mangled the indents. And I don’t think the &nbsp; workaround described in post #27 works inside comments either.

        If you e-mail me the modified script, or PM it to me (inside a *[

    • Usacomp2k3
    • 12 years ago

    BTW, I don’t know if python can be considered modern, JBI ๐Ÿ˜‰

      • just brew it!
      • 12 years ago

      (and #15) – Although it is true that Python has been around for a while, it is still being actively developed — the latest 2.x release was last month, and a major overhaul of the entire language (3.x) is currently in alpha. It is also still used for a lot of new development projects. I’d say that ought to qualify it as “modern”?

        • SpotTheCat
        • 12 years ago

        I liked python!

        I started on scheme, which was very natural in how you think about it, but very un-natural with its syntax. Python fixed it.

    • ludi
    • 12 years ago

    Shouldn’t that title read “40-year-old toy meets 17-year-old scripting language? :-))

    • newbie_of_jan0502
    • 12 years ago

    AutoCad and array is a lot of fun too.

    • ssidbroadcast
    • 12 years ago

    This has potential applications for producing some neato-looking desktop backgrounds. You’d have to take the paths (maybe) through Maya, then retouch in photoshop. Eventually, you’d get something that looks a little like the cover-article image.

    • just brew it!
    • 12 years ago

    Hmm… the indentation of the script fragments seems to have gotten mangled by TR’s content management system. It looked OK in preview mode.

    Unfortunately, indentation matters in Python.

    I’ll have to see if I can figure out how to fix that tonight when I get home…

      • Forge
      • 12 years ago

      I was kind of wondering who Mike Uchima was. ๐Ÿ™‚

      Cool math toy you rigged up there.

      • radix
      • 12 years ago

      It looks ok to me in the Windows Vim editor. Neatly idented and commented as well ๐Ÿ˜‰ Suggestion of improvement for version 0.2: drop the recursion for gcd. Just kidding!

      • just brew it!
      • 12 years ago

      Well it looks OK now; maybe Steve tweaked the CMS.

      If you are playing with the script, you should be able to replace the Draw method in the original script with any of the ones shown in the blog post simply by copy-pasting the code from your browser window into the script.

      I really encourage anyone who’s interested to experiment with it. Messing around with stuff like this can be a fun way to introduce yourself to basic programming concepts (if you’ve never programmed before), or learn a new programming language (if you’re already a software developer).

        • Cyril
        • 12 years ago

        q[< Well it looks OK now; maybe Steve tweaked the CMS.<]q That was me. ๐Ÿ˜‰ You just need to manually insert &nbsp;'s to emulate tabs.

          • just brew it!
          • 12 years ago

          Ahh, thank you!

    • Hdfisise
    • 12 years ago

    reminds me of the spyrograph opera widget which is pretty damn awesome.

    • sparkman
    • 12 years ago

    “Pyrograph”… great name for a Python Spirograph

      • Majiir Paktu
      • 12 years ago

      What about ‘s_[

    • Usacomp2k3
    • 12 years ago

    I had one of those back in 1993 when I was 8. What fun.

    • wingless
    • 12 years ago

    I used to make more complex designs than those in LOGO when I was a kid back in the early ’90s..

      • timbits
      • 12 years ago

      oh yeah? well my dad can beat up your dad!

    • UberGerbil
    • 12 years ago

    So I presume extending this to a third dimension is left as an exercise for the reader? ๐Ÿ™‚

    Nice work. I recall doing something like this, though less intentionally and far less generally, in Applesoft Basic on the Apple ][ — watching the thing draw (at full 1MHz speed) was kind of trance-inducing.

      • mortifiedPenguin
      • 12 years ago

      I suppose the simplest way of extending this to three dimensions is to simply pick and axis and “lathe” it. This wouldn’t be all that interesting to look at though (mostly just a ball with ridges), so perhaps using “lofting” with a second line set in the third dimension would look a bit more fun.

        • UberGerbil
        • 12 years ago

        Replace “circle” with “sphere” in the third paragraph of the post.

          • Majiir Paktu
          • 12 years ago

          And what is then produced, a path? It’s still the same. You’d need to determine the pen’s position on the sphere, as opposed to just the radius.

            • YodaTech
            • 12 years ago

            Let’s hear it for spherical coordinates! =P

Pin It on Pinterest

Share This