Рисование в полярных координатах


Данная программа рисует интересные геометрические кривые внутри некоторой
окружности. Эффект достигается простым приемом: по внутренней поверхности
окружности радиуса R катится диск радиуса r1. На поверхности этого диска
на расстоянии r2 от центра прикреплен карандаш, который рисует узоры. Форма
и размер узоров зависят от радиусов, единичного угла поворота, и временной
длиной пауз, на которые карандаш отрывается от листа бумаги. К программе я
наваял небольшой интерфейс для удобства использования и возможность
сохранять результат в файл.

Если говорить строго, то представленные кривые являются Эпитрохоидами.
Эпитрохоида - плоская кривая, образуемая точкой, жёстко связанной с
окружностью, катящейся по внешней стороне другой окружности.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

#############################################################
#
# Polar, Python, 2011-01-20
# 
# Artix, master@7masterov.ru, icq:53666599, skype:artixmaster
# 
# * Error in code? Nothing is perfect!
# * Free source for free Linux, use it fo free!
# * Please, do not remove this comment!nts!
#
#  x=R*cos(a)
#  y=R*sin(a)
#  L = 2*pi*R, 10<=R<=600
#  l = 2*pi*r, 5<=r<=R
#  n = L/l = R/r
#  b = a*n
#  a - единичный поворот
#  N - количество оборотов
#
#############################################################

from Tkinter import *
from tkMessageBox import *
from tkFileDialog import *
from random import *
from math import *

class HelpDialog(Toplevel):
    def __init__(self, parent):
        Toplevel.__init__(self, parent)
        self.title("Help")
        Label(self,text="Artix, igor@7masterov.ru, (C) 2011").grid(
            row=0,padx=5,pady=5, sticky=W+E)
        Button(self,text="Close",command=self.destroy).grid(
            row=1,padx=5, pady=5, sticky=W+E)

class Application(Frame):
    button = None
    label = None
    menu = None
    menuFile = None
    menuHelp = None
    status = None
    canvas = None
    
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.master.title("Application")
        self.master.minsize(width=500,height=400) 
        w = 500; h = 400
        ws = self.master.winfo_screenwidth()
        hs = self.master.winfo_screenheight()
        x = (ws/2) - (w/2)
        y = (hs/2) - (h/2)
        self.master.geometry('%dx%d+%d+%d' % (w, h, x, y))
        self.master.resizable(width=NO, height=NO)
        self.pack()
		
        self.menu = Menu(self)
        self.menuFile = Menu(self.menu, tearoff=0)
        self.menuFile.add_command(label="New <space>",command=self.onClick)
        self.menuFile.add_command(label="Save as ...",command=self.onSave)
        self.menuFile.add_separator()
        self.menuFile.add_command(label="Exit",command=self.onQuit)
        self.menu.add_cascade(label="File",menu=self.menuFile)
        self.menuHelp = Menu(self.menu, tearoff=0)
        self.menuHelp.add_command(label="Help",command=self.onHelp)
        self.menuHelp.add_command(label="About",command=self.onAbout)
        self.menu.add_cascade(label="Help",menu=self.menuHelp)
        self.master.config(menu=self.menu)
	
        self.canvas = Canvas(self,width=500,height=400,bg="black")
        self.canvas.pack()
        self.canvas.bind('<space>',self.onSpace)
        self.canvas.focus_set()
        self.canvas.create_line(10,10,200,200)
        self.drawPolar()

    def center(self,obj):
        ws = self.master.winfo_screenwidth()
        hs = self.master.winfo_screenheight()
        w = obj.winfo_reqwidth()
        h = obj.winfo_reqheight()
        x = (ws/2) - (w/2)
        y = (hs/2) - (h/2)
        obj.geometry('+%d+%d' % (x, y))
        
    def drawPolar(self):
        self.canvas.delete(ALL)
        self.canvas.create_rectangle(0,0,self.canvas.winfo_reqwidth(),
            self.canvas.winfo_reqheight(),fill="black")
        rnd = Random()
        R = rnd.random()*self.canvas.winfo_reqheight()/5+50
        r1 = rnd.random()*(R+1)+10
        r2 = rnd.random()*(r1-1)+10
        step = rnd.random()*45
        pause = int(rnd.random()*3)+1
        self.rotate(R,r1,r2,step,pause,30)

    def rotate(self, R, r1, r2, a, p, N):
        color_list = ['blue', 'red', 'black']
        angle1 = 0.0
        angle2 = 0.0
        b = a*(R/r1)
        cx = self.canvas.winfo_reqwidth()/2.0
        cy = self.canvas.winfo_reqheight()/2.0
        x1 = 0
        y1 = 0
        loop = 0
        loop1 = 0
        loop2 = 0
        while True:
            if loop1>N or loop2>2*N: break
            x = R*cos(pi/180.0*angle1)
            y = R*sin(pi/180.0*angle1)
            sx = r2*cos(pi/180.0*angle2)
            sy = r2*sin(pi/180.0*angle2)
            x2 = int(cx + (x+sx))
            y2 = int(cy - (y+sy))
            if x1==0 and y1==0:
                x1 = x2; y1 = y2
            if x1!=x2 and y1!=y2 and loop % p == 0:
                color = "#%02X%02X%02X" % (0, loop % 200+55, 0)
                self.canvas.create_line(x1,y1,x2,y2,fill=color)
            angle1+=a
            angle2-=b
            x1 = x2
            y1 = y2
            if angle1>=360:
                angle1-=360
                loop1+=1
            if angle2<=-360:
                angle2+=360
                loop2+=1
            loop+=1
                      

    def onSave(self):
        f = asksaveasfilename(
           filetypes=[('PostScript', '*.ps')],defaultextension='.ps')
        if f!='':
            self.canvas.postscript(file=f)
	
    def onClick(self):
        self.drawPolar()

    def onSpace(self,event):
        self.drawPolar()

    def onQuit(self):
        if askokcancel("Quit", "Do you really wish to quit?"):
            self.quit()

    def onHelp(self):
        dialog = HelpDialog(self)
        self.center(dialog)
        self.master.wait_window(dialog)

    def onAbout(self):
        showinfo("Header", "Message")


if __name__ == "__main__":
    root = Tk()
    app = Application(root)
    root.mainloop()