Created
August 6, 2018 06:19
-
-
Save jsbain/d7ec034a50f5e88e2821c94164c41a52 to your computer and use it in GitHub Desktop.
turtle2.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#\input texinfo | |
# coding: utf-8 | |
# ui-based iOS port of the turtle module (not 100% compatible with standard library | |
# turtle module, but most things beginners would use should work) | |
import ui | |
from math import * | |
import math | |
import time | |
import sys | |
import numbers | |
import threading | |
import objc_util | |
PY3 = sys.version_info[0] >= 3 | |
if PY3: | |
basestring = str | |
_default_size = min(ui.get_screen_size()) - 100 | |
_main_view = None | |
_default_pen = None | |
_default_color_mode = 1.0 | |
_canvas_size = max(ui.get_screen_size()) | |
_canvas_image = None | |
_all_pens = [] | |
# When the pen is set to 'fastest', this is how many drawing commands are batched: | |
batch_size = 100 | |
def Shape(shape,pts): | |
assert (shape=='polygon') | |
p=ui.Path() | |
p.move_to(*pts[0]) | |
for pt in pts[1:]: | |
p.line_to(*pt) | |
p.close() | |
return p | |
class Vec2D(tuple): | |
"""A 2 dimensional vector class, used as a helper class | |
for implementing turtle graphics. | |
May be useful for turtle graphics programs also. | |
Derived from tuple, so a vector is a tuple! | |
Provides (for a, b vectors, k number): | |
a+b vector addition | |
a-b vector subtraction | |
a*b inner product | |
k*a and a*k multiplication with scalar | |
|a| absolute value of a | |
a.rotate(angle) rotation | |
""" | |
def __new__(cls, x, y): | |
return tuple.__new__(cls, (x, y)) | |
def __add__(self, other): | |
return Vec2D(self[0]+other[0], self[1]+other[1]) | |
def __mul__(self, other): | |
if isinstance(other, Vec2D): | |
return self[0]*other[0]+self[1]*other[1] | |
return Vec2D(self[0]*other, self[1]*other) | |
def __rmul__(self, other): | |
if isinstance(other, int) or isinstance(other, float): | |
return Vec2D(self[0]*other, self[1]*other) | |
def __sub__(self, other): | |
return Vec2D(self[0]-other[0], self[1]-other[1]) | |
def __neg__(self): | |
return Vec2D(-self[0], -self[1]) | |
def __abs__(self): | |
return (self[0]**2 + self[1]**2)**0.5 | |
def rotate(self, angle): | |
"""rotate self counterclockwise by angle | |
""" | |
perp = Vec2D(-self[1], self[0]) | |
angle = angle * math.pi / 180.0 | |
c, s = math.cos(angle), math.sin(angle) | |
return Vec2D(self[0]*c+perp[0]*s, self[1]*c+perp[1]*s) | |
def __getnewargs__(self): | |
return (self[0], self[1]) | |
def __repr__(self): | |
return "(%.2f,%.2f)" % self | |
class PenView (ui.View): | |
def __init__(self): | |
self.shape='turtle' | |
self._shapes = { | |
"arrow" : Shape("polygon", ((-10,0), (10,0), (0,10))), | |
"turtle" : Shape("polygon", ((0,16), (-2,14), (-1,10), (-4,7), | |
(-7,9), (-9,8), (-6,5), (-7,1), (-5,-3), (-8,-6), | |
(-6,-8), (-4,-5), (0,-7), (4,-5), (6,-8), (8,-6), | |
(5,-3), (7,1), (6,5), (9,8), (7,9), (4,7), (1,10),(2,14))), | |
"circle" : Shape("polygon", ((10,0), (9.51,3.09), (8.09,5.88), | |
(5.88,8.09), (3.09,9.51), (0,10), (-3.09,9.51), | |
(-5.88,8.09), (-8.09,5.88), (-9.51,3.09), (-10,0), | |
(-9.51,-3.09), (-8.09,-5.88), (-5.88,-8.09), | |
(-3.09,-9.51), (-0.00,-10.00), (3.09,-9.51), | |
(5.88,-8.09), (8.09,-5.88), (9.51,-3.09))), | |
"square" : Shape("polygon", ((10,-10), (10,10), (-10,10), | |
(-10,-10))), | |
"triangle" : Shape("polygon", ((10,-5.77), (0,11.55), | |
(-10,-5.77))), | |
"classic": Shape("polygon", ((0,0),(-5,-9),(0,-7),(5,-9))), | |
"blank": Shape("polygon", ((0,0),(0,0))) | |
} | |
def draw(self): | |
if _canvas_image: | |
_canvas_image.draw() | |
for pen in _all_pens: | |
if not pen._turtle_visible: | |
continue | |
with ui.GState(): | |
ui.concat_ctm(ui.Transform.translation(self.superview.width/2, self.superview.height/2)) | |
pen_tip = self._shapes[self.shape] | |
ui.concat_ctm(ui.Transform.translation(*pen._pos)) | |
ui.concat_ctm(ui.Transform.rotation(pen._angle-math.radians(90))) | |
ui.set_color(pen._current_color) | |
pen_tip.fill() | |
def set_needs_display(self, make_visible=True): | |
ui.View.set_needs_display(self) | |
if make_visible: | |
_main_view.present() | |
_pen_view = PenView() | |
class TurtleView (ui.View): | |
def __init__(self, *args, **kwargs): | |
ui.View.__init__(self, *args, **kwargs) | |
self.name = 'Turtle Graphics' | |
self.canvas_view = ui.View(frame=(0, 0, _default_size, _default_size), bg_color='white') | |
self.bg_color = (0.9, 0.9, 0.9, 1.0) | |
self.tint_color = (0.2, 0.2, 0.2, 1.0) | |
self.add_subview(self.canvas_view) | |
clear_btn_item = ui.ButtonItem('Clear', action=self.clear_action) | |
save_btn_item = ui.ButtonItem('Save', action=self.save_action) | |
self.right_button_items = [save_btn_item, clear_btn_item] | |
_pen_view.frame = self.canvas_view.bounds | |
_pen_view.flex = 'WH' | |
self.canvas_view.add_subview(_pen_view) | |
self.title_label = ui.Label(frame=(0, 0, self.width, 40), flex='W') | |
self.title_label.alignment = ui.ALIGN_CENTER | |
self.title_label.text = 'Turtle Graphics' | |
self.title_label.text_color = (0.4, 0.4, 0.4, 1.0) | |
self.title_label.font = ('<System>', 14) | |
self.add_subview(self.title_label) | |
save_button = ui.Button(image=ui.Image('iow:ios7_download_outline_24'), flex='LB') | |
save_button.frame = (self.width - 40, 0, 40, 40) | |
save_button.action = self.save_action | |
self.add_subview(save_button) | |
def layout(self): | |
# NOTE: When this is called, the previous imports might already have gone out of scope. | |
import ui | |
center = ui.Point(self.width/2, self.height/2) | |
self.canvas_view.frame = (center.x - self.canvas_view.width/2, center.y - self.canvas_view.height/2, self.canvas_view.width, self.canvas_view.height) | |
def present(self): | |
objc_view = objc_util.ObjCInstance(self) | |
user_info = {'view': objc_view} | |
objc_util.ObjCClass('NSNotificationCenter').defaultCenter().postNotificationName_object_userInfo_('ConsoleShowGraphicsViewNotification', None, user_info) | |
def reset(self): | |
self.setup(_default_size, _default_size) | |
global _canvas_image | |
_canvas_image = None | |
_default_pen.reset() | |
_all_pens[:] = [_default_pen] | |
self.canvas_view.bg_color = 'white' | |
self.title('Turtle Graphics') | |
def clear_action(self, sender): | |
# NOTE: Used by clearscreen, could also be attached to a UI element (but isn't currently) | |
global _canvas_image | |
_canvas_image = None | |
_pen_view.set_needs_display() | |
def save_action(self, sender): | |
# NOTE: When this is called, the previous imports might already have gone out of scope. | |
import photos, dialogs, ui | |
with ui.ImageContext(self.canvas_view.width, self.canvas_view.height) as ctx: | |
self.canvas_view.draw_snapshot() | |
img = ctx.get_image() | |
photos.save_image(img) | |
if not photos.is_authorized(): | |
dialogs.hud_alert('Cannot Save', 'error') | |
else: | |
dialogs.hud_alert('Saved to Photos') | |
def title(self, title_str): | |
self.name = str(title_str) | |
self.title_label.text = title_str | |
def tracer(self, n=None, delay=None): | |
if delay is not None: | |
for pen in self.turtles(): | |
pen.delay_sec = delay / 100.0 | |
def delay(self, delay=None): | |
if delay is not None: | |
for pen in self.turtles(): | |
pen.delay_sec = delay / 100.0 | |
def setup(self, width=None, height=None, **kwargs): | |
if width is None: | |
width = _default_size | |
if height is None: | |
height = _default_size | |
self.canvas_view.bounds = (0, 0, width, height) | |
def bgcolor(self, *args): | |
if len(args) == 1 and isinstance(args[0], basestring): | |
self.canvas_view.bg_color = ui.parse_color(args[0]) | |
elif len(args) == 1: | |
r, g, b = args[0] | |
if _default_pen._colormode == 255: | |
r, g, b = (r/255.0, g/255.0, b/255.0) | |
self.canvas_view.bg_color = (r, g, b) | |
elif len(args) == 3: | |
r, g, b = args | |
if _default_pen._colormode == 255: | |
r, g, b = (r/255.0, g/255.0, b/255.0) | |
self.canvas_view.bg_color = (r, g, b) | |
def turtles(self): | |
return _all_pens | |
def clearscreen(self): | |
self.clear_action(None) | |
def update(self): | |
pass | |
class Pen (object): | |
def __init__(self, *args, **kwargs): | |
self._use_rad = False | |
self._current_color = (0, 0, 0, 1) | |
self._current_fill_color = (0, 0, 0, 1) | |
self._colormode = _default_color_mode | |
self._line_width = 1.0 | |
self._angle = 0.0 | |
self._pos = ui.Point(0, 0) | |
self._turtle_visible = True | |
self.is_down = True | |
self.fill_path = None | |
self.delay_sec = 0.01 | |
self.anim_steps = 4 | |
self.canvas_size = ui.Size(512, 512) | |
self.batched_drawings = [] | |
_all_pens.append(self) | |
def _sleep(self): | |
time.sleep(self.delay_sec) | |
def degrees(self): | |
""" Set angle measurement units to degrees.""" | |
self._use_rad = False | |
def radians(self): | |
""" Set angle measurement units to radians.""" | |
self._use_rad = True | |
def _color_args_to_rgba(self, args): | |
"""Helper method to convert various color formats to r,g,b,a (with 0.0-1.0 ranges)""" | |
r, g, b, a = 0, 0, 0, 1 | |
if len(args) >= 3: | |
r, g, b = args[:3] | |
if self._colormode == 255: | |
r, g, b = (r/255.0, g/255.0, b/255.0) | |
a = args[3] if len(args) > 3 else 1.0 | |
elif len(args) == 1 and isinstance(args[0], basestring): | |
r, g, b, a = ui.parse_color(args[0]) | |
elif len(args) == 1: | |
r, g, b = args[0][:3] | |
if self._colormode == 255: | |
r, g, b = (r/255.0, g/255.0, b/255.0) | |
a = args[0][3] if len(args[0]) > 3 else 1.0 | |
return (r, g, b, a) | |
def _angle_to_rad(self, angle): | |
if self._use_rad: | |
return angle | |
return math.radians(angle) | |
def _add_drawing(self, draw_func, force_flush=False): | |
if self.delay_sec == 0: | |
self.batched_drawings.append(draw_func) | |
if len(self.batched_drawings) < batch_size and not force_flush: | |
return | |
with ui.ImageContext(_canvas_size, _canvas_size) as ctx: | |
with ui.autoreleasepool(): | |
global _canvas_image | |
if _canvas_image: | |
_canvas_image.draw(0, 0) | |
ui.concat_ctm(ui.Transform.translation(_pen_view.superview.width/2, _pen_view.superview.height/2)) | |
if self.batched_drawings: | |
for batched_draw_func in self.batched_drawings: | |
batched_draw_func() | |
self.batched_drawings = [] | |
_canvas_image = ctx.get_image() | |
self.update_view() | |
else: | |
draw_func() | |
_canvas_image = ctx.get_image() | |
def _force_flush(self): | |
if self.batched_drawings: | |
self._add_drawing(lambda: None, force_flush=True) | |
def reset(self): | |
"""Delete the turtle's drawings and restore its default values. | |
No argument. | |
Delete the turtle's drawings from the screen, re-center the turtle | |
and set variables to the default values. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.position() | |
(0.00,-22.00) | |
>>> turtle.heading() | |
100.0 | |
>>> turtle.reset() | |
>>> turtle.position() | |
(0.00,0.00) | |
>>> turtle.heading() | |
0.0 | |
""" | |
self._pos = ui.Point(0, 0) | |
self._current_color = (0, 0, 0, 1) | |
self._current_fill_color = (0, 0, 0, 1) | |
self._line_width = 1.0 | |
self._angle = 0.0 | |
self._turtle_visible = True | |
self.is_down = True | |
self.fill_path = None | |
self.batched_drawings = [] | |
self.colormode(255) | |
self.update_view(make_visible=False) | |
def clone(self): | |
"""Create and return a clone of the turtle. | |
No argument. | |
Create and return a clone of the turtle with same position, heading | |
and turtle properties. | |
Example (for a Turtle instance named mick): | |
mick = Turtle() | |
joe = mick.clone() | |
""" | |
p = Pen() | |
p._pos = self._pos | |
p._angle = self._angle | |
p._current_color = self._current_color | |
p._current_fill_color = self._current_fill_color | |
p._turtle_visible = self._turtle_visible | |
p._line_width = self._line_width | |
return p | |
def speed(self, speed_value=None): | |
""" Return or set the turtle's speed. | |
Optional argument: | |
speed -- an integer in the range 0..10 or a speedstring (see below) | |
Set the turtle's speed to an integer value in the range 0 .. 10. | |
If no argument is given: return current speed. | |
If input is a number greater than 10 or smaller than 0.5, | |
speed is set to 0. | |
Speedstrings are mapped to speedvalues in the following way: | |
'fastest' : 0 | |
'fast' : 10 | |
'normal' : 6 | |
'slow' : 3 | |
'slowest' : 1 | |
speeds from 1 to 10 enforce increasingly faster animation of | |
line drawing and turtle turning. | |
Attention: | |
speed = 0 : *no* animation takes place. forward/back makes turtle jump | |
and likewise left/right make the turtle turn instantly. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.speed(3) | |
""" | |
# TODO: The mapping is incorrect, fastest is 10 here, but should be 0. | |
if speed_value is not None: | |
speed_delays = {'fastest': 0.0 , 'fast': 0.01, 'normal': 0.03, 'slow': 0.05, 'slowest': 0.1} | |
if isinstance(speed_value, basestring): | |
self.delay_sec = speed_delays.get(speed_value, 0.03) | |
elif isinstance(speed_value, int): | |
self.delay_sec = 0.0 if (speed_value >= 10 or speed_value <= 0) else (10 - speed_value) / 100.0 | |
return 10 if self.delay_sec == 0 else min(10, max(0, (1.0/self.delay_sec)/10 - 1)) | |
def delay(self, ms): | |
""" Return or set the drawing delay in milliseconds. | |
Optional argument: | |
delay -- positive integer | |
Example (for a TurtleScreen instance named screen): | |
>>> screen.delay(15) | |
>>> screen.delay() | |
15 | |
""" | |
self.delay_sec = min(1.0, ms / 100.0) | |
return self.delay_sec * 100.0 | |
def _draw_line(self, from_pos, to_pos, color, line_width): | |
line_path = ui.Path() | |
line_path.move_to(*from_pos) | |
line_path.line_to(*to_pos) | |
line_path.line_width = line_width | |
line_path.line_cap_style = ui.LINE_CAP_ROUND | |
line_path.line_join_style = ui.LINE_JOIN_ROUND | |
ui.set_color(color) | |
line_path.stroke() | |
def _forward(self, d): | |
self._sleep() | |
new_pos = self._pos + (cos(self._angle) * d, sin(self._angle) * d) | |
if self.fill_path: | |
self.fill_path.line_to(*new_pos) | |
if self.is_down: | |
from_pos = self._pos | |
line_width = self._line_width | |
color = self._current_color | |
def _draw(): | |
self._draw_line(from_pos, new_pos, color, line_width) | |
self._add_drawing(_draw) | |
self._pos = new_pos | |
self.update_view() | |
def forward(self, d): | |
"""Move the turtle forward by the specified distance. | |
Aliases: forward | fd | |
Argument: | |
distance -- a number (integer or float) | |
Move the turtle forward by the specified distance, in the direction | |
the turtle is headed. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.position() | |
(0.00, 0.00) | |
>>> turtle.forward(25) | |
>>> turtle.position() | |
(25.00,0.00) | |
>>> turtle.forward(-75) | |
>>> turtle.position() | |
(-50.00,0.00) | |
""" | |
for i in range(self.anim_steps): | |
self._forward(float(d)/self.anim_steps) | |
fd = forward | |
def backward(self, d): | |
"""Move the turtle backward by distance. | |
Aliases: back | backward | bk | |
Argument: | |
distance -- a number | |
Move the turtle backward by distance ,opposite to the direction the | |
turtle is headed. Do not change the turtle's heading. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.position() | |
(0.00, 0.00) | |
>>> turtle.backward(30) | |
>>> turtle.position() | |
(-30.00, 0.00) | |
""" | |
self.forward(-d) | |
back = backward | |
bk = backward | |
def _left(self, angle): | |
self._sleep() | |
self._angle -= self._angle_to_rad(angle) | |
self.update_view() | |
def left(self, angle): | |
"""Turn turtle left by angle units. | |
Aliases: left | lt | |
Argument: | |
angle -- a number (integer or float) | |
Turn turtle left by angle units. (Units are by default degrees, | |
but can be set via the degrees() and radians() functions.) | |
Angle orientation depends on mode. (See this.) | |
Example (for a Turtle instance named turtle): | |
>>> turtle.heading() | |
22.0 | |
>>> turtle.left(45) | |
>>> turtle.heading() | |
67.0 | |
""" | |
for i in range(self.anim_steps): | |
self._left(float(angle)/self.anim_steps) | |
lt = left | |
def right(self, angle): | |
"""Turn turtle right by angle units. | |
Aliases: right | rt | |
Argument: | |
angle -- a number (integer or float) | |
Turn turtle right by angle units. (Units are by default degrees, | |
but can be set via the degrees() and radians() functions.) | |
Angle orientation depends on mode. (See this.) | |
Example (for a Turtle instance named turtle): | |
>>> turtle.heading() | |
22.0 | |
>>> turtle.right(45) | |
>>> turtle.heading() | |
337.0 | |
""" | |
self.left(-angle) | |
rt = right | |
def begin_fill(self): | |
"""Called just before drawing a shape to be filled. | |
No argument. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.color("black", "red") | |
>>> turtle.begin_fill() | |
>>> turtle.circle(60) | |
>>> turtle.end_fill() | |
""" | |
if self.fill_path: | |
self.end_fill() | |
self.fill_path = ui.Path() | |
self.fill_path.move_to(*self._pos) | |
def end_fill(self): | |
"""Fill the shape drawn after the call begin_fill(). | |
No argument. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.color("black", "red") | |
>>> turtle.begin_fill() | |
>>> turtle.circle(60) | |
>>> turtle.end_fill() | |
""" | |
if not self.fill_path: | |
return | |
fill_path = self.fill_path | |
fill_color = self._current_fill_color | |
def _draw(): | |
ui.set_color(fill_color) | |
fill_path.fill() | |
self._add_drawing(_draw) | |
self.fill_path = None | |
self.update_view() | |
def fill(self, flag): | |
if bool(flag): | |
self.begin_fill() | |
else: | |
self.end_fill() | |
def filling(self): | |
"""Return fillstate (True if filling, False else). | |
No argument. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.begin_fill() | |
>>> if turtle.filling(): | |
... turtle.pensize(5) | |
... else: | |
... turtle.pensize(3) | |
""" | |
return self.fill_path is not None | |
def color(self, *args): | |
"""Return or set the pencolor and fillcolor. | |
Arguments: | |
Several input formats are allowed. | |
They use 0, 1, 2, or 3 arguments as follows: | |
color() | |
Return the current pencolor and the current fillcolor | |
as a pair of color specification strings as are returned | |
by pencolor and fillcolor. | |
color(colorstring), color((r,g,b)), color(r,g,b) | |
inputs as in pencolor, set both, fillcolor and pencolor, | |
to the given value. | |
color(colorstring1, colorstring2), | |
color((r1,g1,b1), (r2,g2,b2)) | |
equivalent to pencolor(colorstring1) and fillcolor(colorstring2) | |
and analogously, if the other input format is used. | |
If turtleshape is a polygon, outline and interior of that polygon | |
is drawn with the newly set colors. | |
For mor info see: pencolor, fillcolor | |
Example (for a Turtle instance named turtle): | |
>>> turtle.color('red', 'green') | |
>>> turtle.color() | |
('red', 'green') | |
>>> colormode(255) | |
>>> color((40, 80, 120), (160, 200, 240)) | |
>>> color() | |
('#285078', '#a0c8f0') | |
""" | |
if len(args) == 1 or len(args) >= 3: | |
self.fillcolor(*args) | |
self.pencolor(*args) | |
elif len(args) == 2: | |
self.pencolor(args[0]) | |
self.fillcolor(args[1]) | |
return self.pencolor(), self.fillcolor() | |
def pencolor(self, *args): | |
""" Return or set the pencolor. | |
Arguments: | |
Four input formats are allowed: | |
- pencolor() | |
Return the current pencolor as color specification string, | |
possibly in hex-number format (see example). | |
May be used as input to another color/pencolor/fillcolor call. | |
- pencolor(colorstring) | |
s is a Tk color specification string, such as "red" or "yellow" | |
- pencolor((r, g, b)) | |
*a tuple* of r, g, and b, which represent, an RGB color, | |
and each of r, g, and b are in the range 0..colormode, | |
where colormode is either 1.0 or 255 | |
- pencolor(r, g, b) | |
r, g, and b represent an RGB color, and each of r, g, and b | |
are in the range 0..colormode | |
If turtleshape is a polygon, the outline of that polygon is drawn | |
with the newly set pencolor. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.pencolor('brown') | |
>>> tup = (0.2, 0.8, 0.55) | |
>>> turtle.pencolor(tup) | |
>>> turtle.pencolor() | |
'#33cc8c' | |
""" | |
if args: | |
r, g, b, a = self._color_args_to_rgba(args) | |
self._current_color = (r, g, b, a) | |
return self._current_color | |
def fillcolor(self, *args): | |
""" Return or set the fillcolor. | |
Arguments: | |
Four input formats are allowed: | |
- fillcolor() | |
Return the current fillcolor as color specification string, | |
possibly in hex-number format (see example). | |
May be used as input to another color/pencolor/fillcolor call. | |
- fillcolor(colorstring) | |
s is a Tk color specification string, such as "red" or "yellow" | |
- fillcolor((r, g, b)) | |
*a tuple* of r, g, and b, which represent, an RGB color, | |
and each of r, g, and b are in the range 0..colormode, | |
where colormode is either 1.0 or 255 | |
- fillcolor(r, g, b) | |
r, g, and b represent an RGB color, and each of r, g, and b | |
are in the range 0..colormode | |
If turtleshape is a polygon, the interior of that polygon is drawn | |
with the newly set fillcolor. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.fillcolor('violet') | |
>>> col = turtle.pencolor() | |
>>> turtle.fillcolor(col) | |
>>> turtle.fillcolor(0, .5, 0) | |
""" | |
if args: | |
r, g, b, a = self._color_args_to_rgba(args) | |
self._current_fill_color = (r, g, b, a) | |
return self._current_fill_color | |
def width(self, w=None): | |
"""Set or return the line thickness. | |
Aliases: pensize | width | |
Argument: | |
width -- positive number | |
Set the line thickness to width or return it. If resizemode is set | |
to "auto" and turtleshape is a polygon, that polygon is drawn with | |
the same line thickness. If no argument is given, current pensize | |
is returned. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.pensize() | |
1 | |
>>> turtle.pensize(10) # from here on lines of width 10 are drawn | |
""" | |
if w is not None: | |
self._line_width = w | |
return self._line_width | |
pensize = width | |
def down(self): | |
"""Pull the pen down -- drawing when moving. | |
Aliases: pendown | pd | down | |
No argument. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.pendown() | |
""" | |
self.is_down = True | |
pendown = down | |
pd = down | |
def up(self): | |
"""Pull the pen up -- no drawing when moving. | |
Aliases: penup | pu | up | |
No argument | |
Example (for a Turtle instance named turtle): | |
>>> turtle.penup() | |
""" | |
self.is_down = False | |
penup = up | |
pu = up | |
def isdown(self): | |
"""Return True if pen is down, False if it's up. | |
No argument. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.penup() | |
>>> turtle.isdown() | |
False | |
>>> turtle.pendown() | |
>>> turtle.isdown() | |
True | |
""" | |
return self.is_down | |
def clear(self): | |
"""Delete the turtle's drawings from the screen. Do not move turtle. | |
No arguments. | |
Delete the turtle's drawings from the screen. Do not move turtle. | |
State and position of the turtle as well as drawings of other | |
turtles are not affected. | |
Examples (for a Turtle instance named turtle): | |
>>> turtle.clear() | |
""" | |
self.fill_path = None | |
global _canvas_image | |
_canvas_image = None | |
self.update_view() | |
def write(self, text, move=False, align=None, font=None): | |
"""Write text at the current turtle position. | |
Arguments: | |
arg -- info, which is to be written to the TurtleScreen | |
move (optional) -- True/False | |
align (optional) -- one of the strings "left", "center" or right" | |
font (optional) -- a triple (fontname, fontsize, fonttype) | |
Write text - the string representation of arg - at the current | |
turtle position according to align ("left", "center" or right") | |
and with the given font. | |
If move is True, the pen is moved to the bottom-right corner | |
of the text. By default, move is False. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.write('Home = ', True, align="center") | |
>>> turtle.write((0,0), True) | |
""" | |
# TODO: Implement `align` and `font` parameters | |
text = str(text) | |
w, h = ui.measure_string(text) | |
color = self._current_color | |
pos = self._pos | |
def _draw(): | |
ui.set_color(color) | |
ui.draw_string(text, (pos.x, pos.y - h, 0, 0)) | |
self._add_drawing(_draw) | |
if move: | |
self._pos += (w, 0) | |
self.update_view() | |
def circle(self, radius, extent=None, steps=None): | |
""" Draw a circle with given radius. | |
Arguments: | |
radius -- a number | |
extent (optional) -- a number | |
steps (optional) -- an integer | |
Draw a circle with given radius. The center is radius units left | |
of the turtle; extent - an angle - determines which part of the | |
circle is drawn. If extent is not given, draw the entire circle. | |
If extent is not a full circle, one endpoint of the arc is the | |
current pen position. Draw the arc in counterclockwise direction | |
if radius is positive, otherwise in clockwise direction. Finally | |
the direction of the turtle is changed by the amount of extent. | |
As the circle is approximated by an inscribed regular polygon, | |
steps determines the number of steps to use. If not given, | |
it will be calculated automatically. Maybe used to draw regular | |
polygons. | |
call: circle(radius) # full circle | |
--or: circle(radius, extent) # arc | |
--or: circle(radius, extent, steps) | |
--or: circle(radius, steps=6) # 6-sided polygon | |
Example (for a Turtle instance named turtle): | |
>>> turtle.circle(50) | |
>>> turtle.circle(120, 180) # semicircle | |
""" | |
if extent is None: | |
extent = math.radians(360) | |
else: | |
extent = math.radians(extent) if not self._use_rad else extent | |
frac = abs(extent) / math.radians(360) | |
if steps is None or steps == 0: | |
steps = 1 + int(min(11 + abs(radius)/6.0, 59.0) * frac) | |
elif not isinstance(steps, int): | |
raise TypeError('steps must be an integer') | |
w = 1.0 * extent / steps | |
w2 = 0.5 * w | |
l = 2.0 * radius * sin(w2) | |
if radius < 0: | |
l, w, w2 = -l, -w, -w2 | |
if not self._use_rad: | |
w, w2 = math.degrees(w), math.degrees(w2) | |
self._left(w2) | |
for i in range(steps): | |
self._forward(l) | |
self._left(w) | |
self._left(-w2) | |
def dot(self, size=None, *color): | |
"""Draw a dot with diameter size, using color. | |
Optional arguments: | |
size -- an integer >= 1 (if given) | |
color -- a colorstring or a numeric color tuple | |
Draw a circular dot with diameter size, using color. | |
If size is not given, the maximum of pensize+4 and 2*pensize is used. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.dot() | |
>>> turtle.fd(50); turtle.dot(20, "blue"); turtle.fd(50) | |
""" | |
if size is None: | |
size = max(self._line_width + 4, self._line_width * 2) | |
if not isinstance(size, numbers.Number): | |
raise TypeError('size must be a number or None') | |
pos = self.pos() | |
current_color = self._current_color | |
def draw_dot(): | |
dot_path = ui.Path.oval(pos[0] - size*0.5, pos[1] - size*0.5, size, size) | |
if color: | |
rgba = self._color_args_to_rgba(color) | |
ui.set_color(rgba) | |
else: | |
ui.set_color(current_color) | |
dot_path.fill() | |
self._add_drawing(draw_dot) | |
def shape(self, shapename): | |
if shapename in _pen_view._shapes: | |
_pen_view.shape=shapename | |
def stamp(): | |
# Not implemented | |
return 0 | |
def clearstamp(self, stampid): | |
# Not implemented | |
pass | |
def clearstamps(self, n=None): | |
# Not implemented | |
pass | |
def undo(self): | |
#---todo: keep buffer of n previous images for undoing? | |
pass | |
def goto(self, x, y=None): | |
"""Move turtle to an absolute position. | |
Aliases: setpos | setposition | goto: | |
Arguments: | |
x -- a number or a pair/vector of numbers | |
y -- a number None | |
call: goto(x, y) # two coordinates | |
--or: goto((x, y)) # a pair (tuple) of coordinates | |
--or: goto(vec) # e.g. as returned by pos() | |
Move turtle to an absolute position. If the pen is down, | |
a line will be drawn. The turtle's orientation does not change. | |
Example (for a Turtle instance named turtle): | |
>>> tp = turtle.pos() | |
>>> tp | |
(0.00, 0.00) | |
>>> turtle.setpos(60,30) | |
>>> turtle.pos() | |
(60.00,30.00) | |
>>> turtle.setpos((20,80)) | |
>>> turtle.pos() | |
(20.00,80.00) | |
>>> turtle.setpos(tp) | |
>>> turtle.pos() | |
(0.00,0.00) | |
""" | |
if y is None: | |
return self.goto(*x) | |
self._sleep() | |
if self.fill_path: | |
self.fill_path.line_to(x, -y) | |
if self.is_down: | |
pos = self._pos | |
color = self._current_color | |
line_width = self._line_width | |
def _draw(): | |
self._draw_line(pos, (x, -y), color, line_width) | |
self._add_drawing(_draw) | |
self._pos = ui.Point(x, -y) | |
self.update_view() | |
setpos = goto | |
setposition = goto | |
def home(self): | |
"""Move turtle to the origin - coordinates (0,0). | |
No arguments. | |
Move turtle to the origin - coordinates (0,0) and set its | |
heading to its start-orientation (which depends on mode). | |
Example (for a Turtle instance named turtle): | |
>>> turtle.home() | |
""" | |
self.goto(0, 0) | |
def towards(self, *args): | |
"""Return the angle of the line from the turtle's position to (x, y). | |
Arguments: | |
x -- a number or a pair/vector of numbers or a turtle instance | |
y -- a number None None | |
call: distance(x, y) # two coordinates | |
--or: distance((x, y)) # a pair (tuple) of coordinates | |
--or: distance(vec) # e.g. as returned by pos() | |
--or: distance(mypen) # where mypen is another turtle | |
Return the angle, between the line from turtle-position to position | |
specified by x, y and the turtle's start orientation. (Depends on | |
modes - "standard" or "logo") | |
Example (for a Turtle instance named turtle): | |
>>> turtle.pos() | |
(10.00, 10.00) | |
>>> turtle.towards(0,0) | |
225.0 | |
""" | |
if len(args) == 1: | |
args = args[0] | |
if isinstance(args, Pen): | |
x, y = args._pos | |
elif len(args) == 2: | |
x, y = args | |
else: | |
raise TypeError('Expected x, y or Pen object') | |
dx, dy = x - self._pos.x, y - self._pos.y | |
a = math.atan2(dy, dx) | |
if not self._use_rad: | |
a = math.degrees(a) | |
return a | |
def heading(self): | |
""" Return the turtle's current heading. | |
No arguments. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.left(67) | |
>>> turtle.heading() | |
67.0 | |
""" | |
if self._use_rad: | |
return self._angle | |
return math.degrees(self._angle) | |
def setheading(self, angle): | |
"""Set the orientation of the turtle to to_angle. | |
Aliases: setheading | seth | |
Argument: | |
to_angle -- a number (integer or float) | |
Set the orientation of the turtle to to_angle. | |
Here are some common directions in degrees: | |
standard - mode: logo-mode: | |
-------------------|-------------------- | |
0 - east 0 - north | |
90 - north 90 - east | |
180 - west 180 - south | |
270 - south 270 - west | |
Example (for a Turtle instance named turtle): | |
>>> turtle.setheading(90) | |
>>> turtle.heading() | |
90 | |
""" | |
self._sleep() | |
self._angle = self._angle_to_rad(angle) | |
self.update_view() | |
seth = setheading | |
def position(self): | |
"""Return the turtle's current location (x,y), as a Vec2D-vector. | |
Aliases: pos | position | |
No arguments. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.pos() | |
(0.00, 240.00) | |
""" | |
return Vec2D(self._pos[0], -self._pos[1]) | |
pos = position | |
def xcor(self): | |
""" Return the turtle's x coordinate. | |
No arguments. | |
Example (for a Turtle instance named turtle): | |
>>> reset() | |
>>> turtle.left(60) | |
>>> turtle.forward(100) | |
>>> print turtle.xcor() | |
50.0 | |
""" | |
return self._pos.x | |
def ycor(self): | |
""" Return the turtle's y coordinate | |
--- | |
No arguments. | |
Example (for a Turtle instance named turtle): | |
>>> reset() | |
>>> turtle.left(60) | |
>>> turtle.forward(100) | |
>>> print turtle.ycor() | |
86.6025403784 | |
""" | |
return -self._pos.y | |
def distance(self, x, y=None): | |
"""Return the distance from the turtle to (x,y) in turtle step units. | |
Arguments: | |
x -- a number or a pair/vector of numbers or a turtle instance | |
y -- a number None None | |
call: distance(x, y) # two coordinates | |
--or: distance((x, y)) # a pair (tuple) of coordinates | |
--or: distance(vec) # e.g. as returned by pos() | |
--or: distance(mypen) # where mypen is another turtle | |
Example (for a Turtle instance named turtle): | |
>>> turtle.pos() | |
(0.00, 0.00) | |
>>> turtle.distance(30,40) | |
50.0 | |
>>> pen = Turtle() | |
>>> pen.forward(77) | |
>>> turtle.distance(pen) | |
77.0 | |
""" | |
if y is None: | |
x, y = x | |
return abs(self._pos - (x, y)) | |
def setx(self, x): | |
"""Set the turtle's first coordinate to x | |
Argument: | |
x -- a number (integer or float) | |
Set the turtle's first coordinate to x, leave second coordinate | |
unchanged. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.position() | |
(0.00, 240.00) | |
>>> turtle.setx(10) | |
>>> turtle.position() | |
(10.00, 240.00) | |
""" | |
self._sleep() | |
self._pos = ui.Point(x, self._pos.y) | |
self.update_view() | |
def sety(self, y): | |
"""Set the turtle's second coordinate to y | |
Argument: | |
y -- a number (integer or float) | |
Set the turtle's first coordinate to x, second coordinate remains | |
unchanged. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.position() | |
(0.00, 40.00) | |
>>> turtle.sety(-10) | |
>>> turtle.position() | |
(0.00, -10.00) | |
""" | |
self._sleep() | |
self._pos = ui.Point(self._pos.x, -y) | |
self.update_view() | |
def pen(self, pen=None, **pendict): | |
"""Return or set the pen's attributes. | |
NOTE: resizemode, stretchfactor, outline, tilt are not supported in Pythonista | |
Arguments: | |
pen -- a dictionary with some or all of the below listed keys. | |
**pendict -- one or more keyword-arguments with the below | |
listed keys as keywords. | |
Return or set the pen's attributes in a 'pen-dictionary' | |
with the following key/value pairs: | |
"shown" : True/False | |
"pendown" : True/False | |
"pencolor" : color-string or color-tuple | |
"fillcolor" : color-string or color-tuple | |
"pensize" : positive number | |
"speed" : number in range 0..10 | |
"resizemode" : "auto" or "user" or "noresize" | |
"stretchfactor": (positive number, positive number) | |
"shearfactor": number | |
"outline" : positive number | |
"tilt" : number | |
This dictionary can be used as argument for a subsequent | |
pen()-call to restore the former pen-state. Moreover one | |
or more of these attributes can be provided as keyword-arguments. | |
This can be used to set several pen attributes in one statement. | |
Examples (for a Turtle instance named turtle): | |
>>> turtle.pen(fillcolor="black", pencolor="red", pensize=10) | |
>>> turtle.pen() | |
{'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1, | |
'pencolor': 'red', 'pendown': True, 'fillcolor': 'black', | |
'stretchfactor': (1,1), 'speed': 3, 'shearfactor': 0.0} | |
>>> penstate=turtle.pen() | |
>>> turtle.color("yellow","") | |
>>> turtle.penup() | |
>>> turtle.pen() | |
{'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1, | |
'pencolor': 'yellow', 'pendown': False, 'fillcolor': '', | |
'stretchfactor': (1,1), 'speed': 3, 'shearfactor': 0.0} | |
>>> p.pen(penstate, fillcolor="green") | |
>>> p.pen() | |
{'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1, | |
'pencolor': 'red', 'pendown': True, 'fillcolor': 'green', | |
'stretchfactor': (1,1), 'speed': 3, 'shearfactor': 0.0} | |
""" | |
current_pen_info = {'shown': self.isvisible(), | |
'pendown': self.isdown(), | |
'pencolor': self._current_color, | |
'fillcolor': self._current_fill_color, | |
'pensize': self._line_width, | |
'speed': 10 if self.delay_sec == 0 else min(10, max(0, (1.0/self.delay_sec)/10 - 1)) | |
} | |
if pen is None: | |
pen = {} | |
if not pen_dict is None: | |
pen.update(pen_dict) | |
if 'shown' in pen: | |
self.showturtle() if pen['shown'] else self.hideturtle() | |
if 'pendown' in pen: | |
self.pendown() if pen['pendown'] else self.penup() | |
if 'pencolor' in pen: | |
self.pencolor(pen['pencolor']) | |
if fillcolor in pen: | |
self.fillcolor(pen['fillcolor']) | |
if 'pensize' in pen: | |
self.width(pen['pensize']) | |
if 'speed' in pen: | |
self.speed(pen['speed']) | |
return current_pen_info | |
def showturtle(self): | |
"""Makes the turtle visible. | |
Aliases: showturtle | st | |
No argument. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.hideturtle() | |
>>> turtle.showturtle() | |
""" | |
self._turtle_visible = True | |
self.update_view() | |
st = showturtle | |
def hideturtle(self): | |
"""Makes the turtle invisible. | |
Aliases: hideturtle | ht | |
No argument. | |
It's a good idea to do this while you're in the | |
middle of a complicated drawing, because hiding | |
the turtle speeds up the drawing observably. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.hideturtle() | |
""" | |
self._turtle_visible = False | |
self.update_view() | |
ht = hideturtle | |
def isvisible(self): | |
"""Return True if the Turtle is shown, False if it's hidden. | |
No argument. | |
Example (for a Turtle instance named turtle): | |
>>> turtle.hideturtle() | |
>>> print turtle.isvisible(): | |
False | |
""" | |
return self._turtle_visible | |
def getscreen(self): | |
return _main_view | |
def getturtle(self): | |
return self | |
def getpen(self): | |
return self | |
def setundobuffer(self, buffer_size): | |
# Not implemented | |
pass | |
def undobufferentries(self): | |
# Not implemented | |
return 0 | |
def mode(self, mode=None): | |
#--- todo? | |
return 'standard' | |
def colormode(self, cmode=None): | |
if cmode == 1.0 or cmode == 255: | |
self._colormode = cmode | |
global _default_color_mode | |
_default_color_mode = cmode | |
return self._colormode | |
def update_view(self, make_visible=True): | |
if len(self.batched_drawings) > 0: | |
return | |
_pen_view.set_needs_display(make_visible=make_visible) | |
class Turtle (Pen): | |
pass | |
def done(): | |
pass | |
mainloop = done | |
def Screen(): | |
return _main_view | |
def textinput(title, prompt): | |
import dialogs | |
try: | |
return dialogs.input_alert(title, prompt, '', 'OK') | |
except KeyboardInterrupt: | |
return None | |
def numinput(title, prompt, default=None, minval=None, maxval=None): | |
import dialogs | |
default_text = str(default) if default is not None else '' | |
result = None | |
while result is None: | |
entered_text = dialogs.input_alert(title, prompt, default_text, 'OK') | |
try: | |
result = float(entered_text) | |
if minval is not None and result < minval: | |
prompt = 'The number you entered is too small.' | |
result = None | |
elif maxval is not None and result > maxval: | |
prompt = 'The number you entered is too big.' | |
result = None | |
except ValueError: | |
prompt = 'Please enter a number!' | |
return result | |
_main_view = TurtleView() | |
_default_pen = Pen() | |
# Move and draw | |
forward = _default_pen.forward | |
fd = forward | |
backward = _default_pen.backward | |
back = backward | |
bk = backward | |
left = _default_pen.left | |
lt = left | |
right = _default_pen.right | |
rt = right | |
goto = _default_pen.goto | |
setpos = goto | |
setposition = goto | |
setx = _default_pen.setx | |
sety = _default_pen.sety | |
setheading = _default_pen.setheading | |
seth = setheading | |
home = _default_pen.home | |
circle = _default_pen.circle | |
dot = _default_pen.dot | |
stamp = _default_pen.stamp | |
clearstamp = _default_pen.clearstamp | |
clearstamps = _default_pen.clearstamps | |
undo = _default_pen.undo | |
speed = _default_pen.speed | |
# Tell Turtle's state | |
position = _default_pen.position | |
pos = position | |
towards = _default_pen.towards | |
xcor = _default_pen.xcor | |
ycor = _default_pen.ycor | |
heading = _default_pen.heading | |
distance = _default_pen.distance | |
# Setting and measurement | |
degrees = _default_pen.degrees | |
radians = _default_pen.radians | |
# Pen control | |
# Drawing state | |
down = _default_pen.down | |
pendown = down | |
pd = down | |
up = _default_pen.up | |
penup = up | |
pu = up | |
width = _default_pen.width | |
pensize = width | |
isdown = _default_pen.isdown | |
pen = _default_pen.pen | |
# Color control | |
color = _default_pen.color | |
pencolor = _default_pen.pencolor | |
fillcolor = _default_pen.fillcolor | |
# Filling | |
fill = _default_pen.fill | |
begin_fill = _default_pen.begin_fill | |
end_fill = _default_pen.end_fill | |
# More drawing control | |
reset = _main_view.reset | |
clear = _default_pen.clear | |
write = _default_pen.write | |
# Turtle state | |
# Visibility | |
showturtle = _default_pen.showturtle | |
st = showturtle | |
hideturtle = _default_pen.hideturtle | |
ht = hideturtle | |
isvisible = _default_pen.isvisible | |
# Appearance | |
''' | |
todo: | |
shape() | |
resizemode() | |
shapesize() | turtlesize() | |
settiltangle() | |
tiltangle() | |
tilt() | |
''' | |
# Special Turtle methods | |
getturtle = _default_pen.getturtle | |
getpen = getturtle | |
getscreen = _default_pen.getscreen | |
setundobuffer = _default_pen.setundobuffer | |
undobufferentries = _default_pen.undobufferentries | |
''' | |
todo: | |
begin_poly() | |
end_poly() | |
get_poly() | |
window_width() | |
window_height() | |
''' | |
clone = _default_pen.clone | |
filling = _default_pen.filling | |
tracer = _main_view.tracer | |
delay = _default_pen.delay | |
title = _main_view.title | |
setup = _main_view.setup | |
mode = _default_pen.mode | |
colormode = _default_pen.colormode | |
bgcolor = _main_view.bgcolor | |
clearscreen = _main_view.clearscreen | |
def _flush_all_pens(): | |
for p in _all_pens: | |
p._force_flush() | |
import _pykit_atexit | |
_pykit_atexit.register('turtle', _flush_all_pens) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment