Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | # LogoMation-like turtle graphics |
2 | ||
3 | from math import * # Also for export | |
4 | import Tkinter | |
5 | ||
6 | class Error(Exception): | |
7 | pass | |
8 | ||
9 | class RawPen: | |
10 | ||
11 | def __init__(self, canvas): | |
12 | self._canvas = canvas | |
13 | self._items = [] | |
14 | self._tracing = 1 | |
15 | self._arrow = 0 | |
16 | self.degrees() | |
17 | self.reset() | |
18 | ||
19 | def degrees(self, fullcircle=360.0): | |
20 | self._fullcircle = fullcircle | |
21 | self._invradian = pi / (fullcircle * 0.5) | |
22 | ||
23 | def radians(self): | |
24 | self.degrees(2.0*pi) | |
25 | ||
26 | def reset(self): | |
27 | canvas = self._canvas | |
28 | self._canvas.update() | |
29 | width = canvas.winfo_width() | |
30 | height = canvas.winfo_height() | |
31 | if width <= 1: | |
32 | width = canvas['width'] | |
33 | if height <= 1: | |
34 | height = canvas['height'] | |
35 | self._origin = float(width)/2.0, float(height)/2.0 | |
36 | self._position = self._origin | |
37 | self._angle = 0.0 | |
38 | self._drawing = 1 | |
39 | self._width = 1 | |
40 | self._color = "black" | |
41 | self._filling = 0 | |
42 | self._path = [] | |
43 | self._tofill = [] | |
44 | self.clear() | |
45 | canvas._root().tkraise() | |
46 | ||
47 | def clear(self): | |
48 | self.fill(0) | |
49 | canvas = self._canvas | |
50 | items = self._items | |
51 | self._items = [] | |
52 | for item in items: | |
53 | canvas.delete(item) | |
54 | self._delete_turtle() | |
55 | self._draw_turtle() | |
56 | ||
57 | def tracer(self, flag): | |
58 | self._tracing = flag | |
59 | if not self._tracing: | |
60 | self._delete_turtle() | |
61 | self._draw_turtle() | |
62 | ||
63 | def forward(self, distance): | |
64 | x0, y0 = start = self._position | |
65 | x1 = x0 + distance * cos(self._angle*self._invradian) | |
66 | y1 = y0 - distance * sin(self._angle*self._invradian) | |
67 | self._goto(x1, y1) | |
68 | ||
69 | def backward(self, distance): | |
70 | self.forward(-distance) | |
71 | ||
72 | def left(self, angle): | |
73 | self._angle = (self._angle + angle) % self._fullcircle | |
74 | self._draw_turtle() | |
75 | ||
76 | def right(self, angle): | |
77 | self.left(-angle) | |
78 | ||
79 | def up(self): | |
80 | self._drawing = 0 | |
81 | ||
82 | def down(self): | |
83 | self._drawing = 1 | |
84 | ||
85 | def width(self, width): | |
86 | self._width = float(width) | |
87 | ||
88 | def color(self, *args): | |
89 | if not args: | |
90 | raise Error, "no color arguments" | |
91 | if len(args) == 1: | |
92 | color = args[0] | |
93 | if type(color) == type(""): | |
94 | # Test the color first | |
95 | try: | |
96 | id = self._canvas.create_line(0, 0, 0, 0, fill=color) | |
97 | except Tkinter.TclError: | |
98 | raise Error, "bad color string: %r" % (color,) | |
99 | self._set_color(color) | |
100 | return | |
101 | try: | |
102 | r, g, b = color | |
103 | except: | |
104 | raise Error, "bad color sequence: %r" % (color,) | |
105 | else: | |
106 | try: | |
107 | r, g, b = args | |
108 | except: | |
109 | raise Error, "bad color arguments: %r" % (args,) | |
110 | assert 0 <= r <= 1 | |
111 | assert 0 <= g <= 1 | |
112 | assert 0 <= b <= 1 | |
113 | x = 255.0 | |
114 | y = 0.5 | |
115 | self._set_color("#%02x%02x%02x" % (int(r*x+y), int(g*x+y), int(b*x+y))) | |
116 | ||
117 | def _set_color(self,color): | |
118 | self._color = color | |
119 | self._draw_turtle() | |
120 | ||
121 | def write(self, arg, move=0): | |
122 | x, y = start = self._position | |
123 | x = x-1 # correction -- calibrated for Windows | |
124 | item = self._canvas.create_text(x, y, | |
125 | text=str(arg), anchor="sw", | |
126 | fill=self._color) | |
127 | self._items.append(item) | |
128 | if move: | |
129 | x0, y0, x1, y1 = self._canvas.bbox(item) | |
130 | self._goto(x1, y1) | |
131 | self._draw_turtle() | |
132 | ||
133 | def fill(self, flag): | |
134 | if self._filling: | |
135 | path = tuple(self._path) | |
136 | smooth = self._filling < 0 | |
137 | if len(path) > 2: | |
138 | item = self._canvas._create('polygon', path, | |
139 | {'fill': self._color, | |
140 | 'smooth': smooth}) | |
141 | self._items.append(item) | |
142 | self._canvas.lower(item) | |
143 | if self._tofill: | |
144 | for item in self._tofill: | |
145 | self._canvas.itemconfigure(item, fill=self._color) | |
146 | self._items.append(item) | |
147 | self._path = [] | |
148 | self._tofill = [] | |
149 | self._filling = flag | |
150 | if flag: | |
151 | self._path.append(self._position) | |
152 | self.forward(0) | |
153 | ||
154 | def circle(self, radius, extent=None): | |
155 | if extent is None: | |
156 | extent = self._fullcircle | |
157 | x0, y0 = self._position | |
158 | xc = x0 - radius * sin(self._angle * self._invradian) | |
159 | yc = y0 - radius * cos(self._angle * self._invradian) | |
160 | if radius >= 0.0: | |
161 | start = self._angle - 90.0 | |
162 | else: | |
163 | start = self._angle + 90.0 | |
164 | extent = -extent | |
165 | if self._filling: | |
166 | if abs(extent) >= self._fullcircle: | |
167 | item = self._canvas.create_oval(xc-radius, yc-radius, | |
168 | xc+radius, yc+radius, | |
169 | width=self._width, | |
170 | outline="") | |
171 | self._tofill.append(item) | |
172 | item = self._canvas.create_arc(xc-radius, yc-radius, | |
173 | xc+radius, yc+radius, | |
174 | style="chord", | |
175 | start=start, | |
176 | extent=extent, | |
177 | width=self._width, | |
178 | outline="") | |
179 | self._tofill.append(item) | |
180 | if self._drawing: | |
181 | if abs(extent) >= self._fullcircle: | |
182 | item = self._canvas.create_oval(xc-radius, yc-radius, | |
183 | xc+radius, yc+radius, | |
184 | width=self._width, | |
185 | outline=self._color) | |
186 | self._items.append(item) | |
187 | item = self._canvas.create_arc(xc-radius, yc-radius, | |
188 | xc+radius, yc+radius, | |
189 | style="arc", | |
190 | start=start, | |
191 | extent=extent, | |
192 | width=self._width, | |
193 | outline=self._color) | |
194 | self._items.append(item) | |
195 | angle = start + extent | |
196 | x1 = xc + abs(radius) * cos(angle * self._invradian) | |
197 | y1 = yc - abs(radius) * sin(angle * self._invradian) | |
198 | self._angle = (self._angle + extent) % self._fullcircle | |
199 | self._position = x1, y1 | |
200 | if self._filling: | |
201 | self._path.append(self._position) | |
202 | self._draw_turtle() | |
203 | ||
204 | def heading(self): | |
205 | return self._angle | |
206 | ||
207 | def setheading(self, angle): | |
208 | self._angle = angle | |
209 | self._draw_turtle() | |
210 | ||
211 | def window_width(self): | |
212 | width = self._canvas.winfo_width() | |
213 | if width <= 1: # the window isn't managed by a geometry manager | |
214 | width = self._canvas['width'] | |
215 | return width | |
216 | ||
217 | def window_height(self): | |
218 | height = self._canvas.winfo_height() | |
219 | if height <= 1: # the window isn't managed by a geometry manager | |
220 | height = self._canvas['height'] | |
221 | return height | |
222 | ||
223 | def position(self): | |
224 | x0, y0 = self._origin | |
225 | x1, y1 = self._position | |
226 | return [x1-x0, -y1+y0] | |
227 | ||
228 | def setx(self, xpos): | |
229 | x0, y0 = self._origin | |
230 | x1, y1 = self._position | |
231 | self._goto(x0+xpos, y1) | |
232 | ||
233 | def sety(self, ypos): | |
234 | x0, y0 = self._origin | |
235 | x1, y1 = self._position | |
236 | self._goto(x1, y0-ypos) | |
237 | ||
238 | def goto(self, *args): | |
239 | if len(args) == 1: | |
240 | try: | |
241 | x, y = args[0] | |
242 | except: | |
243 | raise Error, "bad point argument: %r" % (args[0],) | |
244 | else: | |
245 | try: | |
246 | x, y = args | |
247 | except: | |
248 | raise Error, "bad coordinates: %r" % (args[0],) | |
249 | x0, y0 = self._origin | |
250 | self._goto(x0+x, y0-y) | |
251 | ||
252 | def _goto(self, x1, y1): | |
253 | x0, y0 = start = self._position | |
254 | self._position = map(float, (x1, y1)) | |
255 | if self._filling: | |
256 | self._path.append(self._position) | |
257 | if self._drawing: | |
258 | if self._tracing: | |
259 | dx = float(x1 - x0) | |
260 | dy = float(y1 - y0) | |
261 | distance = hypot(dx, dy) | |
262 | nhops = int(distance) | |
263 | item = self._canvas.create_line(x0, y0, x0, y0, | |
264 | width=self._width, | |
265 | capstyle="round", | |
266 | fill=self._color) | |
267 | try: | |
268 | for i in range(1, 1+nhops): | |
269 | x, y = x0 + dx*i/nhops, y0 + dy*i/nhops | |
270 | self._canvas.coords(item, x0, y0, x, y) | |
271 | self._draw_turtle((x,y)) | |
272 | self._canvas.update() | |
273 | self._canvas.after(10) | |
274 | # in case nhops==0 | |
275 | self._canvas.coords(item, x0, y0, x1, y1) | |
276 | self._canvas.itemconfigure(item, arrow="none") | |
277 | except Tkinter.TclError: | |
278 | # Probably the window was closed! | |
279 | return | |
280 | else: | |
281 | item = self._canvas.create_line(x0, y0, x1, y1, | |
282 | width=self._width, | |
283 | capstyle="round", | |
284 | fill=self._color) | |
285 | self._items.append(item) | |
286 | self._draw_turtle() | |
287 | ||
288 | def _draw_turtle(self,position=[]): | |
289 | if not self._tracing: | |
290 | return | |
291 | if position == []: | |
292 | position = self._position | |
293 | x,y = position | |
294 | distance = 8 | |
295 | dx = distance * cos(self._angle*self._invradian) | |
296 | dy = distance * sin(self._angle*self._invradian) | |
297 | self._delete_turtle() | |
298 | self._arrow = self._canvas.create_line(x-dx,y+dy,x,y, | |
299 | width=self._width, | |
300 | arrow="last", | |
301 | capstyle="round", | |
302 | fill=self._color) | |
303 | self._canvas.update() | |
304 | ||
305 | def _delete_turtle(self): | |
306 | if self._arrow != 0: | |
307 | self._canvas.delete(self._arrow) | |
308 | self._arrow = 0 | |
309 | ||
310 | ||
311 | ||
312 | _root = None | |
313 | _canvas = None | |
314 | _pen = None | |
315 | ||
316 | class Pen(RawPen): | |
317 | ||
318 | def __init__(self): | |
319 | global _root, _canvas | |
320 | if _root is None: | |
321 | _root = Tkinter.Tk() | |
322 | _root.wm_protocol("WM_DELETE_WINDOW", self._destroy) | |
323 | if _canvas is None: | |
324 | # XXX Should have scroll bars | |
325 | _canvas = Tkinter.Canvas(_root, background="white") | |
326 | _canvas.pack(expand=1, fill="both") | |
327 | RawPen.__init__(self, _canvas) | |
328 | ||
329 | def _destroy(self): | |
330 | global _root, _canvas, _pen | |
331 | root = self._canvas._root() | |
332 | if root is _root: | |
333 | _pen = None | |
334 | _root = None | |
335 | _canvas = None | |
336 | root.destroy() | |
337 | ||
338 | ||
339 | def _getpen(): | |
340 | global _pen | |
341 | pen = _pen | |
342 | if not pen: | |
343 | _pen = pen = Pen() | |
344 | return pen | |
345 | ||
346 | def degrees(): _getpen().degrees() | |
347 | def radians(): _getpen().radians() | |
348 | def reset(): _getpen().reset() | |
349 | def clear(): _getpen().clear() | |
350 | def tracer(flag): _getpen().tracer(flag) | |
351 | def forward(distance): _getpen().forward(distance) | |
352 | def backward(distance): _getpen().backward(distance) | |
353 | def left(angle): _getpen().left(angle) | |
354 | def right(angle): _getpen().right(angle) | |
355 | def up(): _getpen().up() | |
356 | def down(): _getpen().down() | |
357 | def width(width): _getpen().width(width) | |
358 | def color(*args): _getpen().color(*args) | |
359 | def write(arg, move=0): _getpen().write(arg, move) | |
360 | def fill(flag): _getpen().fill(flag) | |
361 | def circle(radius, extent=None): _getpen().circle(radius, extent) | |
362 | def goto(*args): _getpen().goto(*args) | |
363 | def heading(): return _getpen().heading() | |
364 | def setheading(angle): _getpen().setheading(angle) | |
365 | def position(): return _getpen().position() | |
366 | def window_width(): return _getpen().window_width() | |
367 | def window_height(): return _getpen().window_height() | |
368 | def setx(xpos): _getpen().setx(xpos) | |
369 | def sety(ypos): _getpen().sety(ypos) | |
370 | ||
371 | def demo(): | |
372 | reset() | |
373 | tracer(1) | |
374 | up() | |
375 | backward(100) | |
376 | down() | |
377 | # draw 3 squares; the last filled | |
378 | width(3) | |
379 | for i in range(3): | |
380 | if i == 2: | |
381 | fill(1) | |
382 | for j in range(4): | |
383 | forward(20) | |
384 | left(90) | |
385 | if i == 2: | |
386 | color("maroon") | |
387 | fill(0) | |
388 | up() | |
389 | forward(30) | |
390 | down() | |
391 | width(1) | |
392 | color("black") | |
393 | # move out of the way | |
394 | tracer(0) | |
395 | up() | |
396 | right(90) | |
397 | forward(100) | |
398 | right(90) | |
399 | forward(100) | |
400 | right(180) | |
401 | down() | |
402 | # some text | |
403 | write("startstart", 1) | |
404 | write("start", 1) | |
405 | color("red") | |
406 | # staircase | |
407 | for i in range(5): | |
408 | forward(20) | |
409 | left(90) | |
410 | forward(20) | |
411 | right(90) | |
412 | # filled staircase | |
413 | fill(1) | |
414 | for i in range(5): | |
415 | forward(20) | |
416 | left(90) | |
417 | forward(20) | |
418 | right(90) | |
419 | fill(0) | |
420 | # more text | |
421 | write("end") | |
422 | if __name__ == '__main__': | |
423 | _root.mainloop() | |
424 | ||
425 | if __name__ == '__main__': | |
426 | demo() |