-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathspiro.py
298 lines (225 loc) · 7.72 KB
/
spiro.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# spiro.py
# Description: drawing spirographs using math equations with python
# Tutorial from: Spirographs by Playground: Geeky Projects for the Curious
# Programmer
import turtle
import math
import numpy as np
from math import gcd
import sys
import random
import argparse
from PIL import Image
from datetime import datetime
# Class for drawing a Spirograph
class Spiro:
# the constructor
def __init__(self, xc, yc, col, R, r, L):
# crate the turtle object
self.t = turtle.Turtle()
# set the cursor shape
self.t.shape('turtle')
# set the drawings steps/increments in degrees
self.step = 5
# set the drawing complete flag
# see https://docs.python.org/3.3/library/turtle.html
self.drawingComplete = False
# set the paramaters from the functions below
self.setparams(xc, yc, col, R, r, L)
# initialize the drawing
self.restart()
# set the parameters
def setparams(self, xc, yc, col, R, r, L):
# the Spirograph parameters
self.xc = xc
self.yc = yc
self.R = int(R)
self.r = int(r)
self.L = L
# reduce r/R to its smallest form by dividing with the GCD
gcdVal = gcd(self.r, self.R)
self.nRot = self.r//gcdVal
# get ratio of radii
self.k = r/float(R)
# set color
self.t.color(*col)
# store the current angle
self.a = 0
# set restart method for the drawing
def restart(self):
# set the flag
self.drawingComplete = False
# show the turtle
self.t.showturtle()
# go to the first point to begin plot
self.t.up()
R, k, L = self.R, self.k, self.L
a = 0.0
x = R*((1-k)*math.cos(a) + L*k*math.cos((1-k)*a/k))
y = R*((1-k)*math.sin(a) - L*k*math.sin((1-k)*a/k))
self.t.setpos(self.xc + x, self.yc + y)
self.t.down()
# Specify the draw method
def draw(self):
# draw the remaining points
R, k, L, = self.R, self.k, self.L
for i in range(0, 360*self.nRot + 1, self.step):
a = math.radians(i)
x = R*((1-k)*math.cos(a) + L*k*math.cos((1-k)*a/k))
y = R*((1-k)*math.sin(a) - L*k*math.sin((1-k)*a/k))
self.t.setpos(self.xc + x, self.yc + y)
# drawing is done so hide the turtle cursor
self.t.hideturtle()
# update the drawing by one step crating animatin effect
def update(self):
# skip remaining steps if complete
if self.drawingComplete:
return
# increment the angle
self.a += self.step
# draw a step
R, k, L = self.R, self.k, self.L
# set angle
a = math.radians(self.a)
x = self.R*((1-k)*math.cos(a) + L*k*math.cos((1-k)*a/k))
y = self.R*((1-k)*math.sin(a) - L*k*math.sin((1-k)*a/k))
self.t.setpos(self.xc + x, self.yc + y)
# if drawing is complete, set the flag
if self.a >= 360*self.nRot:
self.drawingComplete = True
# drawing is done, so hide turtle cursor
self.t.hideturtle()
# clear everything
def clear(self):
self.t.clear()
# class for animating the spirographs
class SpiroAnimator:
# constructor
def __init__(self, N):
# set the timer value in millisecords
self.deltaT = 10
# get the window dimensions
self.width = turtle.window_width()
self.height = turtle.window_height()
# crate spiro objects
self.spiros = []
for i in range(N):
# generate random parameters
rparams = self.genRandomParams()
# set the spiro parameters
spiro = Spiro(*rparams)
self.spiros.append(spiro)
# call timer
turtle.ontimer(self.update, self.deltaT)
# restart the spiro drawing
def restart(self):
for spiro in self.spiros:
# clear
spiro.clear()
# generate random parameters
rparams = self.genRandomParams()
# set the spiro parameters
spiro.setparams(*rparams)
# restart drawing
spiro.restart()
# generate random parameters
def genRandomParams(self):
width, height = self.width, self.height
R = random.randint(50, min(width, height)//2)
r = random.randint(10, 9*R//10)
L = random.uniform(0.1, 0.9)
xc = random.randint(-width//2, width//2)
yc = random.randint(-height//2, height//2)
col = (random.random(),
random.random(),
random.random()
)
return(xc, yc, col, R, r, L)
# update method
def update(self):
# Update all spiros
numComplete = 0
for spiro in self.spiros:
# update
spiro.update()
# count completed spiros
if spiro.drawingComplete:
numComplete += 1
# restart if all spiros are complete
if numComplete == len(self.spiros):
self.restart()
# call the timer
turtle.ontimer(self.update, self.deltaT)
# toggle cursor
def toggleTurtles(self):
for spiro in self.spiros:
if spiro.t.isvisible():
spiro.t.hideturtle()
else:
spiro.t.showturtle()
# save drawings to PNG files
def saveDrawing():
# hide the turtle cursor
turtle.hideturtle()
# generate unique filenames
dateStr = (datetime.now()).strftime("%d%b%Y-%H%M%S")
fileName = 'spiro-' + dateStr
print('saving drawing to %s.esp/png' % fileName)
# get the tkinter canvas
canvas = turtle.getcanvas()
# save the drawings as a post script image
canvas.postscript(file=fileName + '.eps')
# use the Pillow module to convert the postscript image file to PNG
img = Image.open(fileName + '.eps')
img.save(fileName + '.png', 'png')
# show the turtle cursor
turtle.showturtle()
# The main() function
def main():
# use sys.argv if needed
print('generating spirograph...')
# create parser
descStr = """This program draws Spirographs using the turtle module.
When run with no arguments, this program draws random Spirographs.
Terminology:
R: radius of outer circle
r: radius of inner circle
L: ratio of hole distance to r
"""
parser = argparse.ArgumentParser(description=descStr)
# add expected arguments
parser.add_argument('--sparams', nargs=3, dest='sparams', required=False,
help="The three arguments in sparms: R, r, L")
# parse args
args = parser.parse_args()
# set width of the spiro drawing window 60% of the screen width, height
turtle.setup(width=0.6, height=0.6)
# set shape of the turtle cursor
turtle.shape('turtle')
# set the title to the Spirographs!
turtle.title("Spirographs")
# add the key handler to save your drawings
turtle.onkey(saveDrawing, "s")
# start listening
turtle.listen()
# hide the main turtle cursor
turtle.hideturtle()
# check for any arguments sent to --sparams and draw the Spirograph
if args.sparams:
params = [float(x) for x in args.sparams]
# draw the Spirograph with the given parameters
col = (0.0, 0.0, 0.0)
spiro = Spiro(0, 0, col, *params)
spiro.draw()
else:
# create the animator object
spiroAnim = SpiroAnimator(4)
# add a keyhandler to toggle the turtle cursor
turtle.onkey(spiroAnim.toggleTurtles, "t")
# add a keyhandler to restart the animation
turtle.onkey(spiroAnim.restart, "space")
# start the turtle main loop
turtle.mainloop()
# call main
if __name__ == '__main__':
main()