I've been going through the several suggestions for improvements for the
turtle module. I'm sorry it's taken so long, but here is what I've come
up with.
There were a lot of cool ideas, but I took a somewhat conservative
approach. If something else needs to be added or removed, bring it up.
Please have a look, test it on your old turtle examples, etc. and report
any problems (and their solutions!) back here.
Below is an executive summary of what has been added, and attached is
the new file, called turtlenew.py for the moment, and turtlepatches.txt,
a text file explaining the changes in more detail.
So let's see what you all think.
Cheers,
Vern Ceder
----
Several additions have been made to turtle.py:
To RawPen:
speed(speed=2)
sets speed from 0 (very slow) to 4 (as fast as possible). 2 is
equivalent to the old hard coded default
To Pen:
changed __init__ to use specific window geometry from module variables
To module:
setup((w,h,x,y))
sets module window geometry variables, must be called before window
is opened
title(newtitle)
sets module variable for title, must be called before window is opened
done()
starts an event loop (as the demo does at the end) to keep window
from hanging if run in a program from IDLE. This is meant to be the last
command of program, it doesn't do much for interactive sessions.
speed(speed=2)
sets default speed for Pens created after setting. See speed()
under RawPen
Turtle()
added as an alias (subclass, actually) of Pen()
Bug fixes:
* Removed self._canvas.lower(item) in fill, which caused filled
polygons to move themselves to the back layer
* removed local variable pen from _getpen()
--
This time for sure!
-Bullwinkle J. Moose
-----------------------------
Vern Ceder, Director of Technology
Canterbury School, 3210 Smith Road, Ft Wayne, IN 46804
[EMAIL PROTECTED]; 260-436-0746; FAX: 260-436-5137
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"edupython" group.
To post to this group, send email to [EMAIL PROTECTED]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at http://groups.google.com/group/edupython
-~----------~----~----~----~------~----~------~--~---
Changes to turtle.py
--------------------
These are mostly minor additions of functionality to make things more
convient when using turtle.py for introductory work. The intent is
to be compatible with all existing turtle examples. Further,
everything should work whether importing the module, importing Pen
or Turtle, or importing everything into the namespace with *.
1. remove self._canvas.lower(item) from fill()
* this causes filled polygons to put themselves behind everything, while
circles and lines do not
2. added a _delay variable to module and to RawPen to control the
speed of drawing. Also added a speed() method to RawPen to map
speeds from 0 (slowest) to 4 (fastest, _delay = 0). The default
remains 10, the value hard-coded in the original
* Being able to watch the turtle as it draws is good, but sometimes
it moves too slowly for complex figures
* the change also allows each turtle to move at a different
speed. Not necessary, but cool anyway
3. added _width, _height, _startx, _starty and _title variables to
module. Pen's __init__ now uses them to setup the geometry of the
window when it opens. Also added a setup() function to set those
variables, needed if you import * from turtle
* by default the window opened is too small for interesting
projects.
* the setup() function takes a tuple for width, height, startx and
starty positions. There may be a better way to do this.
* the default window geometry I chose - 50% of window width by 75%
of height, starting at 0,0 - may not be the most convenient
4. Also set a default window title. Added a title() function to module
to set initial title display.
5. add a done() function to start a main event loop (just as the demo
does). Otherwise, the window is unresponsive if in a program run
from IDLE.
6. aliased (subclassed, actually) Pen() with Turtle() so that one may
create Turtle objects after importing from turtle.py. Saying that
they were turtles but calling the objects pens always seemed odd to
some of us.
7. removed the odd 3 way shuffle in _getpen(). Is there a reason for
this that I'm missing?
Good ideas not implemented yet
------------------------------
There were several good suggestions that I haven't added yet, either
because they are problematic or because I'm not sure how best to do it:
1. the addition of a TurtleCanvas class to manage things like
resizing, background color, etc. I'm not sure about the best way
to do this and would prefer suggestions from someone who has a
clear idea of how they might be used in teaching. It should also be
dead simple to use...
2. any fixes for the "hanging turtle" problem, which seems to be a
tkinter problem more than a turtle problem.
3. "getter" methods for Pen properties like line width (and now speed,
I suppose) and many others. As Gregor suggests they should all be
done the same way. But I'm not sure how much we need them for
beginners.
4. improved documentation - Toby has offered to look at this
5. Chris Smith has some bug fixes and refactorings that he is going to
submit via the patch manager. We can also add those if we want
after we get through this step.# LogoMation-like turtle graphics
from math import * # Also for export
import Tkinter
class Error(Exception):
pass
_delay = 10
class RawPen:
def __init__(self, canvas):
self._canvas = canvas
self._items = []
self._tracing = 1
self._arrow = 0
self._delay = _delay
self.degrees()
self.reset()
def degrees(self, fullcircle=360.0):
self._fullcircle = fullcircle
self._invradian = pi / (fullcircle * 0.5)
def radians(self):
self.degrees(2.0*pi)
def reset(self):
canvas = self._canvas
self._canvas.update()
width = canvas.winfo_width()
height = canvas.winfo_height()
if width <= 1:
width = canvas['width']
if height <= 1:
height = canvas['height']
self._origin = float(width)/2.0, float(height)/2.0
self._position = self._origin
self._angle = 0.0
self._drawing = 1
self._width = 1
self._color = "black"
self._filling = 0
self._path = []
self._tofill = []
self.clear()
canvas._root().tkraise()
def clear(self):
self.fill(0)
canvas = self._canvas
items = self._items
self._items = []
for item in items:
canvas.delete(item)
self._delete_turtle()
self._draw_turtle()
def tracer(self, flag):
self._tracing = flag
if not self._tracing:
self._delete_turtle()
self._draw_turtle()
def forward(self, distance):
x0, y0 = start = self._position
x1 = x0 + distance * cos(self._angle*self._invradian)
y1 = y0 - distance * sin(self._angle*self._invradian)
self._goto(x1, y1)
def backward(self, distance):
self.forward(-distance)
def left(self, angle):
self._angle = (self._angle + angle) % self._fullcircle
self._draw_turtle()
def right(self, angle):
self.left(-angle)
def up(self):
self._drawing = 0
def down(self):
self._drawing = 1
def width(self, width):
self._width = float(width)
def color(self, *args):
if not args:
raise Error, "no color arguments"
if len(args) == 1:
color = args[0]
if type(color) == type(""):
# Test the color first
try:
id = self._canvas.create_line(0, 0, 0, 0, fill=color)
except Tkinter.TclError:
raise Error, "bad color string: %r" % (color,)
self._set_color(color)
return
try:
r, g, b = color
except:
raise Error, "bad color sequence: %r" % (color,)
else:
try:
r, g, b = args
except:
raise Error, "bad color arguments: %r" % (args,)
assert 0 <= r <= 1
assert 0 <= g <= 1
assert 0 <= b <= 1
x = 255.0
y = 0.5
self._set_color("#%02x%02x%02x" % (int(r*x+y), int(g*x+y), int(b*x+y)))
def _set_color(self,color):
self._color = color
self._draw_turtle()
def write(self, arg, move=0):
x, y = start = self._position
x = x-1 # correction -- calibrated for Windows
item = self._canvas.create_text(x, y,
text=str(arg), anchor="sw",
fill=self._color)
self._items.append(item)
if move:
x0, y0, x1, y1 = self._canvas.bbox(item)
self._goto(x1, y1)
self._draw_turtle()
def fill(self, flag):
if self._filling:
path = tuple(self._path)
smooth = self._filling < 0
if len(path) > 2:
item = self._canvas._create('polygon', path,
{'fill': self._color,
'smooth': smooth})
self._items.append(item)
# self._canvas.lower(item)
if self._tofill:
for item in self._tofill:
self._canvas.itemconfigure(item, fill=self._color)
self._items.append(item)
self._path = []
self._tofill = []
self._filling = flag
if flag:
self._path.append(self._position)
self.forward(0)
def circle(self, radius, extent=None):
if extent is None:
extent = self._fullcircle
x0, y0 = self._position
xc = x0 - radius * sin(self._angle * self._invradian)
yc = y0 - radius * cos(self._angle * self._invradian)
if radius >= 0.0:
start = self._angle - 90.0
else:
start = self._angle + 90.0
extent = -extent
if self._filling:
if abs(extent) >= self._fullcircle:
item = self._canvas.create_oval(xc-radius, yc-radius,
xc+radius, yc+radius,
width=self._width,
outline="")
self._tofill.append(item)
item = self._canvas.create_arc(xc-radius, yc-radius,
xc+radius, yc+radius,
style="chord",
start=start,
extent=extent,
width=self._width,
outline="")
self._tofill.append(item)
if self._drawing:
if abs(extent) >= self._fullcircle:
item = self._canvas.create_oval(xc-radius, yc-radius,
xc+radius, yc+radius,
width=self._width,
outline=self._color)
self._items.append(item)
item = self._canvas.create_arc(xc-radius, yc-radius,
xc+radius, yc+radius,
style="arc",
start=start,
extent=extent,
width=self._width,
outline=self._color)
self._items.append(item)
angle = start + extent
x1 = xc + abs(radius) * cos(angle * self._invradian)
y1 = yc - abs(radius) * sin(angle * self._invradian)
self._angle = (self._angle + extent) % self._fullcircle
self._position = x1, y1
if self._filling:
self._path.append(self._position)
self._draw_turtle()
def heading(self):
return self._angle
def setheading(self, angle):
self._angle = angle
self._draw_turtle()
def window_width(self):
width = self._canvas.winfo_width()
if width <= 1: # the window isn't managed by a geometry manager
width = self._canvas['width']
return width
def window_height(self):
height = self._canvas.winfo_height()
if height <= 1: # the window isn't managed by a geometry manager
height = self._canvas['height']
return height
def position(self):
x0, y0 = self._origin
x1, y1 = self._position
return [x1-x0, -y1+y0]
def setx(self, xpos):
x0, y0 = self._origin
x1, y1 = self._position
self._goto(x0+xpos, y1)
def sety(self, ypos):
x0, y0 = self._origin
x1, y1 = self._position
self._goto(x1, y0-ypos)
def goto(self, *args):
if len(args) == 1:
try:
x, y = args[0]
except:
raise Error, "bad point argument: %r" % (args[0],)
else:
try:
x, y = args
except:
raise Error, "bad coordinates: %r" % (args[0],)
x0, y0 = self._origin
self._goto(x0+x, y0-y)
def _goto(self, x1, y1):
x0, y0 = start = self._position
self._position = map(float, (x1, y1))
if self._filling:
self._path.append(self._position)
if self._drawing:
if self._tracing:
dx = float(x1 - x0)
dy = float(y1 - y0)
distance = hypot(dx, dy)
nhops = int(distance)
item = self._canvas.create_line(x0, y0, x0, y0,
width=self._width,
capstyle="round",
fill=self._color)
try:
for i in range(1, 1+nhops):
x, y = x0 + dx*i/nhops, y0 + dy*i/nhops
self._canvas.coords(item, x0, y0, x, y)
self._draw_turtle((x,y))
self._canvas.update()
self._canvas.after(self._delay)
# in case nhops==0
self._canvas.coords(item, x0, y0, x1, y1)
self._canvas.itemconfigure(item, arrow="none")
except Tkinter.TclError:
# Probably the window was closed!
return
else:
item = self._canvas.create_line(x0, y0, x1, y1,
width=self._width,
capstyle="round",
fill=self._color)
self._items.append(item)
self._draw_turtle()
def speed(self, speed=2):
""" maps a speed in 4 to 0 to delay given _canvas.after() in
_goto()
4 is fastests (0 ms delay)
0 is slowest (20 ms delay)"""
if speed >=0 and speed <=4:
self._delay = 20 - (speed * 5)
def _draw_turtle(self,position=[]):
if not self._tracing:
return
if position == []:
position = self._position
x,y = position
distance = 8
dx = distance * cos(self._angle*self._invradian)
dy = distance * sin(self._angle*self._invradian)
self._delete_turtle()
self._arrow = self._canvas.create_line(x-dx,y+dy,x,y,
width=self._width,
arrow="last",
capstyle="round",
fill=self._color)
self._canvas.update()
def _delete_turtle(self):
if self._arrow != 0:
self._canvas.delete(self._arrow)
self._arrow = 0
_root = None
_canvas = None
_pen = None
_width = 0.50
_height = 0.75
_startx = 0
_starty = 0
_title = "Turtle Graphics"
class Pen(RawPen):
def __init__(self):
global _root, _canvas, _width, _height
if _root is None:
_root = Tkinter.Tk()
_root.wm_protocol("WM_DELETE_WINDOW", self._destroy)
_root.title(_title)
if _width <= 1:
_width = _root.winfo_screenwidth() * _width
if _height <= 1:
_height = _root.winfo_screenheight() * _height
_root.geometry("%dx%d+%d+%d" % (_width, _height,
_startx, _starty))
if _canvas is None:
# XXX Should have scroll bars
_canvas = Tkinter.Canvas(_root, background="white")
_canvas.pack(expand=1, fill="both")
RawPen.__init__(self, _canvas)
def _destroy(self):
global _root, _canvas, _pen
root = self._canvas._root()
if root is _root:
_pen = None
_root = None
_canvas = None
root.destroy()
def _getpen():
global _pen
pen = _pen
if not pen:
_pen = pen = Pen()
return pen
class Turtle(Pen):
pass
def degrees(): _getpen().degrees()
def radians(): _getpen().radians()
def reset(): _getpen().reset()
def clear(): _getpen().clear()
def tracer(flag): _getpen().tracer(flag)
def forward(distance): _getpen().forward(distance)
def backward(distance): _getpen().backward(distance)
def left(angle): _getpen().left(angle)
def right(angle): _getpen().right(angle)
def up(): _getpen().up()
def down(): _getpen().down()
def width(width): _getpen().width(width)
def color(*args): _getpen().color(*args)
def write(arg, move=0): _getpen().write(arg, move)
def fill(flag): _getpen().fill(flag)
def circle(radius, extent=None): _getpen().circle(radius, extent)
def goto(*args): _getpen().goto(*args)
def heading(): return _getpen().heading()
def setheading(angle): _getpen().setheading(angle)
def position(): return _getpen().position()
def window_width(): return _getpen().window_width()
def window_height(): return _getpen().window_height()
def setx(xpos): _getpen().setx(xpos)
def sety(ypos): _getpen().sety(ypos)
def done(): _root.mainloop()
def speed(speed=2): _getpen().speed(speed)
def setup(geometry=()):
global _width, _height, _startx, _starty
if len(geometry) == 4:
_width, _height, _startx, _starty = geometry
def title(title):
global _title
_title = title
def demo():
reset()
tracer(1)
up()
backward(100)
down()
# draw 3 squares; the last filled
width(3)
for i in range(3):
if i == 2:
fill(1)
for j in range(4):
forward(20)
left(90)
if i == 2:
color("maroon")
fill(0)
up()
forward(30)
down()
width(1)
color("black")
# move out of the way
tracer(0)
up()
right(90)
forward(100)
right(90)
forward(100)
right(180)
down()
# some text
write("startstart", 1)
write("start", 1)
color("red")
# staircase
for i in range(5):
forward(20)
left(90)
forward(20)
right(90)
# filled staircase
fill(1)
for i in range(5):
forward(20)
left(90)
forward(20)
right(90)
fill(0)
# more text
write("end")
if __name__ == '__main__':
_root.mainloop()
if __name__ == '__main__':
demo()