#!/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
