Python实现​旋转描记器

Nov 9, 2019

对于数学部分,您可以参考Wiki:https://en.wikipedia.org/wiki/Spirograph#Mathematical_basis

代码实现:

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
#importing the required libraries 
import random, argparse
import math
import turtle
from PIL import Image
from datetime import datetime
from fractions import gcd

# A class that draws a spirograph
class Spiro:
# constructor
def __init__(self, xc, yc, col, R, r, l):

# create own turtle
self.t = turtle.Turtle()
# set cursor shape
self.t.shape('turtle')
# set step in degrees
self.step = 5
# set drawing complete flag
self.drawingComplete = False

# set parameters
self.setparams(xc, yc, col, R, r, l)

# initiatize drawing
self.restart()

# set parameters
def setparams(self, xc, yc, col, R, r, l):
# spirograph parameters
self.xc = xc
self.yc = yc
self.R = int(R)
self.r = int(r)
self.l = l
self.col = col
# reduce r/R to smallest form by dividing with 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)
# current angle
self.a = 0

# restart drawing
def restart(self):
# set flag
self.drawingComplete = False
# show turtle
self.t.showturtle()
# go to first point
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()

# draw the whole thing
def draw(self):
# draw rest of 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)
# done - hide turtle
self.t.hideturtle()

# update by one step
def update(self):
# skip if done
if self.drawingComplete:
return
# increment angle
self.a += self.step
# draw 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)
# check if drawing is complete and set flag
if self.a >= 360*self.nRot:
self.drawingComplete = True
# done - hide turtle
self.t.hideturtle()

# clear everything
def clear(self):
self.t.clear()

# A class for animating spirographs
class SpiroAnimator:
# constructor
def __init__(self, N):
# timer value in milliseconds
self.deltaT = 10
# get window dimensions
self.width = turtle.window_width()
self.height = turtle.window_height()
# create spiro objects
self.spiros = []
for i in range(N):
# generate random parameters
rparams = self.genRandomParams()
# set spiro params
spiro = Spiro(*rparams)
self.spiros.append(spiro)
# call timer
turtle.ontimer(self.update, self.deltaT)

# restart sprio drawing
def restart(self):
for spiro in self.spiros:
# clear
spiro.clear()
# generate random parameters
rparams = self.genRandomParams()
# set spiro params
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)

def update(self):
# update all spiros
nComplete = 0
for spiro in self.spiros:
# update
spiro.update()
# count completed ones
if spiro.drawingComplete:
nComplete+= 1
# if all spiros are complete, restart
if nComplete == len(self.spiros):
self.restart()
# call timer
turtle.ontimer(self.update, self.deltaT)

# toggle turtle on/off
def toggleTurtles(self):
for spiro in self.spiros:
if spiro.t.isvisible():
spiro.t.hideturtle()
else:
spiro.t.showturtle()

# save spiros to image
def saveDrawing():
# hide turtle
turtle.hideturtle()
# generate unique file name
dateStr = (datetime.now()).strftime("%d%b%Y-%H%M%S")
fileName = 'spiro-' + dateStr
print('saving drawing to %s.eps/png' % fileName)
# get tkinter canvas
canvas = turtle.getcanvas()
# save postscipt image
canvas.postscript(file = fileName + '.eps')
# use PIL to convert to PNG
img = Image.open(fileName + '.eps')
img.save(fileName + '.png', 'png')
# show turtle
turtle.showturtle()

# 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 sparams: R, r, l.")


# parse args
args = parser.parse_args()

# set to 80% screen width
turtle.setup(width=0.8)

# set cursor shape
turtle.shape('turtle')

# set title
turtle.title("Spirographs!")
# add key handler for saving images
turtle.onkey(saveDrawing, "s")
# start listening
turtle.listen()

# hide main turtle cursor
turtle.hideturtle()

# checks args and draw
if args.sparams:
params = [float(x) for x in args.sparams]
# draw spirograph with given parameters
# black by default
col = (0.0, 0.0, 0.0)
spiro = Spiro(0, 0, col, *params)
spiro.draw()
else:
# create animator object
spiroAnim = SpiroAnimator(4)
# add key handler to toggle turtle cursor
turtle.onkey(spiroAnim.toggleTurtles, "t")
# add key handler to restart animation
turtle.onkey(spiroAnim.restart, "space")

# start turtle main loop
turtle.mainloop()

# call main
if __name__ == '__main__':
main()

实现效果:

MeyR1S.png