#!/usr/bin/python
# 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
# Utility function to compute greatest common denominator
def gcd(x, y):
if x % y == 0:
return y
else:
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):
# check for impossible values
if fixedRadius == 0 or movingRadius == 0:
return
# 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(penColor)
# 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
# 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):
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 + windowSize / 2 + 0.5)
y1 = floor((-x * sin(rotate) + y * cos(rotate)) * scaleFactor + windowSize / 2 + 0.5)
pointList.append((x1, y1))
# draw the pattern
self.canvas.create_line(pointList, fill = penColor)
# 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