jupyter-turtle 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jupyter_turtle-1.0.1/LICENSE +21 -0
- jupyter_turtle-1.0.1/PKG-INFO +27 -0
- jupyter_turtle-1.0.1/README.md +10 -0
- jupyter_turtle-1.0.1/pyproject.toml +39 -0
- jupyter_turtle-1.0.1/setup.cfg +4 -0
- jupyter_turtle-1.0.1/src/jupyter_turtle/__init__.py +201 -0
- jupyter_turtle-1.0.1/src/jupyter_turtle/turtle.png +0 -0
- jupyter_turtle-1.0.1/src/jupyter_turtle/turtle.py +290 -0
- jupyter_turtle-1.0.1/src/jupyter_turtle.egg-info/PKG-INFO +27 -0
- jupyter_turtle-1.0.1/src/jupyter_turtle.egg-info/SOURCES.txt +11 -0
- jupyter_turtle-1.0.1/src/jupyter_turtle.egg-info/dependency_links.txt +1 -0
- jupyter_turtle-1.0.1/src/jupyter_turtle.egg-info/requires.txt +2 -0
- jupyter_turtle-1.0.1/src/jupyter_turtle.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Mike Matera
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jupyter-turtle
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Simplified Turtle graphics for the Jupyter Notebook
|
|
5
|
+
Author-email: Mike Matera <michael.matera@cabrillo.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mike-matera/jupyter-turtle
|
|
8
|
+
Project-URL: Issues, https://github.com/mike-matera/jupyter-turtle/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: ipycanvas
|
|
15
|
+
Requires-Dist: jupyterlab
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# Jupyter Turtle
|
|
19
|
+
|
|
20
|
+
This is an implementation of simplified Turtle graphics to support my [Python
|
|
21
|
+
for Everyone](https://github.com/mike-matera/python-for-everyone) course.
|
|
22
|
+
[Logo](https://en.wikipedia.org/wiki/Logo_\(programming_language\)) was my first
|
|
23
|
+
programming language. The purpose of this implementation is to give beginners a
|
|
24
|
+
visual experience to help them understand algorithmic reasoning. The module
|
|
25
|
+
contains a `Turtle` class implementation with a full API and a simplified API
|
|
26
|
+
that I use in class.
|
|
27
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Jupyter Turtle
|
|
2
|
+
|
|
3
|
+
This is an implementation of simplified Turtle graphics to support my [Python
|
|
4
|
+
for Everyone](https://github.com/mike-matera/python-for-everyone) course.
|
|
5
|
+
[Logo](https://en.wikipedia.org/wiki/Logo_\(programming_language\)) was my first
|
|
6
|
+
programming language. The purpose of this implementation is to give beginners a
|
|
7
|
+
visual experience to help them understand algorithmic reasoning. The module
|
|
8
|
+
contains a `Turtle` class implementation with a full API and a simplified API
|
|
9
|
+
that I use in class.
|
|
10
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "jupyter-turtle"
|
|
3
|
+
authors = [
|
|
4
|
+
{name = "Mike Matera", email = "michael.matera@cabrillo.edu"},
|
|
5
|
+
]
|
|
6
|
+
description = "Simplified Turtle graphics for the Jupyter Notebook"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">=3.9"
|
|
9
|
+
version = "1.0.1"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICEN[CS]E*"]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"ipycanvas",
|
|
19
|
+
"jupyterlab",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/mike-matera/jupyter-turtle"
|
|
24
|
+
Issues = "https://github.com/mike-matera/jupyter-turtle/issues"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.package-data]
|
|
27
|
+
jupyter_turtle = ["*.png"]
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["setuptools"]
|
|
31
|
+
build-backend = "setuptools.build_meta"
|
|
32
|
+
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
line-length = 79
|
|
35
|
+
|
|
36
|
+
[tool.ruff.format]
|
|
37
|
+
quote-style = "double"
|
|
38
|
+
indent-style = "space"
|
|
39
|
+
docstring-code-format = true
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
from typing import Sequence, Tuple, Union
|
|
3
|
+
|
|
4
|
+
from IPython.display import display
|
|
5
|
+
|
|
6
|
+
from .turtle import DimPoint, Turtle
|
|
7
|
+
|
|
8
|
+
_turtle = None
|
|
9
|
+
|
|
10
|
+
# The simplified API.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _check_turtle():
|
|
14
|
+
global _turtle
|
|
15
|
+
if _turtle is None:
|
|
16
|
+
_turtle = Turtle()
|
|
17
|
+
_turtle.stats = {
|
|
18
|
+
"moves": 0,
|
|
19
|
+
"jumps": 0,
|
|
20
|
+
"strokes": 0,
|
|
21
|
+
"turns": 0,
|
|
22
|
+
"words": [],
|
|
23
|
+
"_last_down": 0,
|
|
24
|
+
}
|
|
25
|
+
display(_turtle)
|
|
26
|
+
return _turtle
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def clear():
|
|
30
|
+
"""Clear the canvas."""
|
|
31
|
+
tu = _check_turtle()
|
|
32
|
+
_turtle.stats = {
|
|
33
|
+
"moves": 0,
|
|
34
|
+
"jumps": 0,
|
|
35
|
+
"strokes": 0,
|
|
36
|
+
"turns": 0,
|
|
37
|
+
"words": [],
|
|
38
|
+
"_last_down": 0,
|
|
39
|
+
}
|
|
40
|
+
tu.clear()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def move(distance: float):
|
|
44
|
+
"""Move the turtle by distance pixels."""
|
|
45
|
+
tu = _check_turtle()
|
|
46
|
+
tu.stats["moves"] += 1
|
|
47
|
+
tu.move(distance)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def turn(degrees: float):
|
|
51
|
+
"""Turn the pen by degrees. Positive numbers turn left, negative numbers
|
|
52
|
+
turn right."""
|
|
53
|
+
tu = _check_turtle()
|
|
54
|
+
tu.stats["turns"] += 1
|
|
55
|
+
tu.turn(degrees)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def pen_up():
|
|
59
|
+
"""Pick the pen up. Movements won't make lines."""
|
|
60
|
+
tu = _check_turtle()
|
|
61
|
+
if tu.stats["moves"] > tu.stats["_last_down"]:
|
|
62
|
+
tu.stats["strokes"] += 1
|
|
63
|
+
tu.pen_up()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def pen_down():
|
|
67
|
+
"""Put the pen down. Movements will make lines."""
|
|
68
|
+
tu = _check_turtle()
|
|
69
|
+
tu.stats["_last_down"] = tu.stats["moves"]
|
|
70
|
+
tu.pen_down()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def show_turtle():
|
|
74
|
+
"""Show the turtle in the scene."""
|
|
75
|
+
tu = _check_turtle()
|
|
76
|
+
tu.show()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def hide_turtle():
|
|
80
|
+
"""Hide the turtle in the scene."""
|
|
81
|
+
tu = _check_turtle()
|
|
82
|
+
tu.hide()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def write(
|
|
86
|
+
text: str, font: str = "24px sans-serif", text_align: str = "center"
|
|
87
|
+
):
|
|
88
|
+
"""Write text.
|
|
89
|
+
|
|
90
|
+
Arguments:
|
|
91
|
+
|
|
92
|
+
text: The text to write
|
|
93
|
+
font: The HTML font specification
|
|
94
|
+
text_align: The alignment of the text relative to the turtle
|
|
95
|
+
"""
|
|
96
|
+
tu = _check_turtle()
|
|
97
|
+
tu.stats["words"].append(text)
|
|
98
|
+
tu.write(text, font, text_align)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def goto(*place: Union[Tuple[int, int], Sequence[int], DimPoint]):
|
|
102
|
+
"""Jump to a point"""
|
|
103
|
+
tu = _check_turtle()
|
|
104
|
+
tu.stats["moves"] += 1
|
|
105
|
+
tu.stats["jumps"] += 1
|
|
106
|
+
tu.pos = place
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def set_background(filename: str):
|
|
110
|
+
"""Set the background image"""
|
|
111
|
+
tu = _check_turtle()
|
|
112
|
+
tu.background(filename)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def set_heading(heading: float):
|
|
116
|
+
"""Set the pen to face heading in degrees."""
|
|
117
|
+
tu = _check_turtle()
|
|
118
|
+
tu.stats["turns"] += 1
|
|
119
|
+
tu.heading = heading
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def set_color(color: Union[str, int]):
|
|
123
|
+
"""Set the pen color using HTML color notation."""
|
|
124
|
+
tu = _check_turtle()
|
|
125
|
+
tu.color = color
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def set_width(width: int):
|
|
129
|
+
"""Set the line thickness."""
|
|
130
|
+
tu = _check_turtle()
|
|
131
|
+
tu.width = width
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def set_size(width: int, height: int):
|
|
135
|
+
"""Set the size of the arena."""
|
|
136
|
+
tu = _check_turtle()
|
|
137
|
+
tu.size = (width, height)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@contextlib.contextmanager
|
|
141
|
+
def polygon(
|
|
142
|
+
*,
|
|
143
|
+
color: str | None = None,
|
|
144
|
+
width: int | None = None,
|
|
145
|
+
fill: str | None = None,
|
|
146
|
+
):
|
|
147
|
+
"""
|
|
148
|
+
Draw a polygon by connecting moves together.
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
|
|
152
|
+
with tu.polygon():
|
|
153
|
+
tu.move(30)
|
|
154
|
+
tu.turn(45)
|
|
155
|
+
tu.move(30)
|
|
156
|
+
tu.turn(45)
|
|
157
|
+
tu.move(30)
|
|
158
|
+
"""
|
|
159
|
+
tu = _check_turtle()
|
|
160
|
+
old_color = tu.color
|
|
161
|
+
old_width = tu.width
|
|
162
|
+
old_fill = tu.fill
|
|
163
|
+
try:
|
|
164
|
+
if color is not None:
|
|
165
|
+
tu.color = color
|
|
166
|
+
if fill is not None:
|
|
167
|
+
tu.fill = fill
|
|
168
|
+
if width is not None:
|
|
169
|
+
tu.width = width
|
|
170
|
+
|
|
171
|
+
with tu.polygon():
|
|
172
|
+
yield
|
|
173
|
+
|
|
174
|
+
finally:
|
|
175
|
+
tu.color = old_color
|
|
176
|
+
tu.fill = old_fill
|
|
177
|
+
tu.width = old_width
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def pre_run_cell(info):
|
|
181
|
+
"""
|
|
182
|
+
Callback before a cell is run.
|
|
183
|
+
"""
|
|
184
|
+
global _turtle
|
|
185
|
+
_turtle = None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def post_run_cell(result):
|
|
189
|
+
"""
|
|
190
|
+
Callback after a cell has run.
|
|
191
|
+
"""
|
|
192
|
+
global _turtle
|
|
193
|
+
result.turtle = _turtle
|
|
194
|
+
if _turtle is not None:
|
|
195
|
+
if _turtle.stats["moves"] > _turtle.stats["_last_down"]:
|
|
196
|
+
_turtle.stats["strokes"] += 1
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def load_ipython_extension(ipython):
|
|
200
|
+
ipython.events.register("pre_run_cell", pre_run_cell)
|
|
201
|
+
ipython.events.register("post_run_cell", post_run_cell)
|
|
Binary file
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A simple implementation of turtle graphics for teaching algorithms.
|
|
3
|
+
|
|
4
|
+
Author: Mike Matera
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import colorsys
|
|
8
|
+
import contextlib
|
|
9
|
+
import math
|
|
10
|
+
import pathlib
|
|
11
|
+
from collections import namedtuple
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
|
|
14
|
+
import numpy
|
|
15
|
+
import PIL
|
|
16
|
+
from ipycanvas import Canvas, MultiCanvas, hold_canvas
|
|
17
|
+
from IPython.display import display
|
|
18
|
+
|
|
19
|
+
DimPoint = namedtuple("DimPoint", ["x", "y"])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Turtle:
|
|
23
|
+
def __init__(self, size: tuple[int, int] = (600, 300), **kwargs):
|
|
24
|
+
"""Create a Turtle drawing canvas.
|
|
25
|
+
|
|
26
|
+
Arguments:
|
|
27
|
+
size: Set the size of the canvas.
|
|
28
|
+
"""
|
|
29
|
+
# Load the Turtle image
|
|
30
|
+
turtle = numpy.array(
|
|
31
|
+
PIL.Image.open(pathlib.Path(__file__).parent / "turtle.png")
|
|
32
|
+
)
|
|
33
|
+
self._turtle = Canvas(width=turtle.shape[0], height=turtle.shape[1])
|
|
34
|
+
self._turtle.put_image_data(turtle)
|
|
35
|
+
|
|
36
|
+
# Create a new Canvas
|
|
37
|
+
self._size = DimPoint(size[0], size[1])
|
|
38
|
+
self._canvas = MultiCanvas(
|
|
39
|
+
n_canvases=3,
|
|
40
|
+
width=self._size.x,
|
|
41
|
+
height=self._size.y,
|
|
42
|
+
sync_image_data=True,
|
|
43
|
+
**kwargs,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Initialize properties
|
|
47
|
+
self._current = self._to_native(DimPoint(0, 0))
|
|
48
|
+
self._cur_heading = (3 * math.pi) / 2 # in Canvas Y is negative.
|
|
49
|
+
self._pendown = True
|
|
50
|
+
self._show = True
|
|
51
|
+
self._fill = None
|
|
52
|
+
self._image = None
|
|
53
|
+
self._polygon = False
|
|
54
|
+
|
|
55
|
+
# Render the turtle
|
|
56
|
+
with self._do_draw():
|
|
57
|
+
self._canvas.clear()
|
|
58
|
+
|
|
59
|
+
@contextlib.contextmanager
|
|
60
|
+
def _do_draw(self):
|
|
61
|
+
"""Context manager that combines all drawing operations and re-renders
|
|
62
|
+
the turtle."""
|
|
63
|
+
with hold_canvas(self._canvas):
|
|
64
|
+
yield
|
|
65
|
+
self._canvas[2].clear()
|
|
66
|
+
if self._show:
|
|
67
|
+
# Redraw the Turtle
|
|
68
|
+
self._canvas[2].save()
|
|
69
|
+
self._canvas[2].translate(self._current.x, self._current.y)
|
|
70
|
+
self._canvas[2].rotate(self._cur_heading + math.pi / 2)
|
|
71
|
+
self._canvas[2].draw_image(
|
|
72
|
+
self._turtle, x=-15, y=-15, width=30, height=30
|
|
73
|
+
)
|
|
74
|
+
self._canvas[2].restore()
|
|
75
|
+
|
|
76
|
+
def _to_native(self, point: tuple[int, int]) -> DimPoint:
|
|
77
|
+
"""Convert Turtle coordinates to native ones."""
|
|
78
|
+
return DimPoint(
|
|
79
|
+
x=self._size.x // 2 + point[0], y=self._size.y // 2 - point[1]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def _to_turtle(self, point: DimPoint) -> tuple[int, int]:
|
|
83
|
+
"""Convert Turtle coordinates to native ones."""
|
|
84
|
+
return (point[0] - self._size.x // 2, self._size.y // 2 - point[1])
|
|
85
|
+
|
|
86
|
+
def _ipython_display_(self):
|
|
87
|
+
display(self._canvas)
|
|
88
|
+
|
|
89
|
+
def _move(self, to: DimPoint):
|
|
90
|
+
"""Low-level move operation."""
|
|
91
|
+
start = self._current
|
|
92
|
+
self._current = to
|
|
93
|
+
with self._do_draw():
|
|
94
|
+
if self._polygon:
|
|
95
|
+
if self._pendown:
|
|
96
|
+
self._canvas[1].line_to(*self._current)
|
|
97
|
+
else:
|
|
98
|
+
self._canvas[1].move_to(*start)
|
|
99
|
+
else:
|
|
100
|
+
if self._pendown:
|
|
101
|
+
self._canvas[1].begin_path()
|
|
102
|
+
self._canvas[1].move_to(*start)
|
|
103
|
+
self._canvas[1].line_to(*self._current)
|
|
104
|
+
self._canvas[1].stroke()
|
|
105
|
+
|
|
106
|
+
def clear(self):
|
|
107
|
+
"""Clear all drawing."""
|
|
108
|
+
self._canvas[1].clear()
|
|
109
|
+
|
|
110
|
+
def move(self, distance: float):
|
|
111
|
+
"""Move the turtle by distance pixels."""
|
|
112
|
+
self._move(
|
|
113
|
+
DimPoint(
|
|
114
|
+
x=self._current.x + math.cos(self._cur_heading) * distance,
|
|
115
|
+
y=self._current.y + math.sin(self._cur_heading) * distance,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def turn(self, degrees: float):
|
|
120
|
+
"""Turn the pen by degrees."""
|
|
121
|
+
with self._do_draw():
|
|
122
|
+
self._cur_heading = (self._cur_heading - math.radians(degrees)) % (
|
|
123
|
+
math.pi * 2
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def pen_up(self):
|
|
127
|
+
"""Pick the pen up. Movements won't make lines."""
|
|
128
|
+
self._pendown = False
|
|
129
|
+
|
|
130
|
+
def pen_down(self):
|
|
131
|
+
"""Put the pen down. Movements will make lines."""
|
|
132
|
+
self._pendown = True
|
|
133
|
+
|
|
134
|
+
def show(self):
|
|
135
|
+
"""Show the turtle in the scene."""
|
|
136
|
+
with self._do_draw():
|
|
137
|
+
self._show = True
|
|
138
|
+
|
|
139
|
+
def hide(self):
|
|
140
|
+
"""Hide the turtle in the scene."""
|
|
141
|
+
with self._do_draw():
|
|
142
|
+
self._show = False
|
|
143
|
+
|
|
144
|
+
def background(self, filename: str):
|
|
145
|
+
"""Set the background image"""
|
|
146
|
+
self._image = numpy.array(PIL.Image.open(filename))
|
|
147
|
+
self.size = (self._image.shape[1], self._image.shape[0])
|
|
148
|
+
self._canvas[0].put_image_data(self._image)
|
|
149
|
+
|
|
150
|
+
@contextlib.contextmanager
|
|
151
|
+
def polygon(self):
|
|
152
|
+
"""Context manager for drawing a polygon."""
|
|
153
|
+
self._polygon = True
|
|
154
|
+
self._canvas[1].begin_path()
|
|
155
|
+
self._canvas[1].move_to(*self._current)
|
|
156
|
+
try:
|
|
157
|
+
yield
|
|
158
|
+
finally:
|
|
159
|
+
self._canvas[1].close_path()
|
|
160
|
+
self._canvas[1].stroke()
|
|
161
|
+
if self._fill is not None:
|
|
162
|
+
self._canvas[1].fill()
|
|
163
|
+
self._polygon = False
|
|
164
|
+
|
|
165
|
+
def write(
|
|
166
|
+
self,
|
|
167
|
+
text: str,
|
|
168
|
+
font: str = "24px sans-serif",
|
|
169
|
+
text_align: str = "center",
|
|
170
|
+
):
|
|
171
|
+
"""Write text.
|
|
172
|
+
|
|
173
|
+
Arguments:
|
|
174
|
+
|
|
175
|
+
text: The text to write
|
|
176
|
+
font: The HTML font specification
|
|
177
|
+
text_align: The alignment of the text relative to the turtle
|
|
178
|
+
line_color: The color of the outline of the text (defaults to the pen color)
|
|
179
|
+
fill_color: The color of the fill of the text (defaults to the pen color)
|
|
180
|
+
"""
|
|
181
|
+
with self._do_draw():
|
|
182
|
+
self._canvas[1].translate(self._current.x, self._current.y)
|
|
183
|
+
self._canvas[1].rotate(self._cur_heading + math.pi / 2)
|
|
184
|
+
self._canvas[1].font = font
|
|
185
|
+
self._canvas[1].text_align = text_align
|
|
186
|
+
self._canvas[1].fill_text(text, 0, 0)
|
|
187
|
+
self._canvas[1].stroke_text(text, 0, 0)
|
|
188
|
+
self._canvas[1].reset_transform()
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def size(self) -> tuple[int, int]:
|
|
192
|
+
"""Get the size of the canvas' color buffer (not layout size)."""
|
|
193
|
+
return (self._size.x, self._size.y)
|
|
194
|
+
|
|
195
|
+
@size.setter
|
|
196
|
+
def size(self, newsize: tuple[int, int]):
|
|
197
|
+
"""Resize the canvas element and adjust they layout size."""
|
|
198
|
+
self._size = DimPoint(x=newsize[0], y=newsize[1])
|
|
199
|
+
with self._do_draw():
|
|
200
|
+
self._canvas.width = self._size.x
|
|
201
|
+
self._canvas.height = self._size.y
|
|
202
|
+
|
|
203
|
+
# Move the turtle to the center
|
|
204
|
+
self._current = self._to_native(DimPoint(0, 0))
|
|
205
|
+
self._cur_heading = (3 * math.pi) / 2 # in Canvas Y is negative.
|
|
206
|
+
|
|
207
|
+
if self._size.x >= 800:
|
|
208
|
+
self._canvas.layout.width = "90%"
|
|
209
|
+
self._canvas.layout.max_width = f"{self._size.x}px"
|
|
210
|
+
self._canvas.layout.min_width = "800px"
|
|
211
|
+
else:
|
|
212
|
+
self._canvas.layout.width = "auto"
|
|
213
|
+
self._canvas.layout.max_width = "auto"
|
|
214
|
+
self._canvas.layout.min_width = "auto"
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def pos(self) -> tuple[int, int]:
|
|
218
|
+
"""Get the current location of the Turtle."""
|
|
219
|
+
return self._to_turtle(self._current)
|
|
220
|
+
|
|
221
|
+
@pos.setter
|
|
222
|
+
def pos(self, *place: tuple[int, int] | Sequence[int] | DimPoint):
|
|
223
|
+
"""Goto a point in the coordinate space."""
|
|
224
|
+
if len(place) == 0:
|
|
225
|
+
raise ValueError("Goto where?")
|
|
226
|
+
elif isinstance(place[0], DimPoint):
|
|
227
|
+
p = place[0]
|
|
228
|
+
elif isinstance(place[0], tuple):
|
|
229
|
+
p = DimPoint._make(*place)
|
|
230
|
+
else:
|
|
231
|
+
p = DimPoint._make(place)
|
|
232
|
+
|
|
233
|
+
self._move(self._to_native(p))
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def heading(self) -> float:
|
|
237
|
+
"""Get the current heading."""
|
|
238
|
+
return -math.degrees(self._cur_heading + math.pi / 2) % 360
|
|
239
|
+
|
|
240
|
+
@heading.setter
|
|
241
|
+
def heading(self, heading: float):
|
|
242
|
+
"""Set the pen to face heading in degrees."""
|
|
243
|
+
with self._do_draw():
|
|
244
|
+
self._cur_heading = math.radians(-heading - 90) % (math.pi * 2)
|
|
245
|
+
|
|
246
|
+
def _hue_to_html(self, hue: int):
|
|
247
|
+
"""Convert a color number from 0 to 365 to HTML"""
|
|
248
|
+
rgb = colorsys.hsv_to_rgb((hue % 365) / 364, 1, 1)
|
|
249
|
+
return f"#{round(rgb[0] * 255):02x}{round(rgb[1] * 255):02x}{round(rgb[2] * 255):02x}"
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def color(self) -> str:
|
|
253
|
+
"""Set the pen color using HTML color notation."""
|
|
254
|
+
return self._canvas[1].stroke_style
|
|
255
|
+
|
|
256
|
+
@color.setter
|
|
257
|
+
def color(self, color: str | int):
|
|
258
|
+
"""Set the pen color using HTML color notation."""
|
|
259
|
+
if color.__class__ in [int, float]:
|
|
260
|
+
color = self._hue_to_html(color)
|
|
261
|
+
self._canvas[1].stroke_style = color
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def fill(self) -> str:
|
|
265
|
+
"""Set the pen color using HTML color notation."""
|
|
266
|
+
return self._fill
|
|
267
|
+
|
|
268
|
+
@fill.setter
|
|
269
|
+
def fill(self, color: str):
|
|
270
|
+
"""Set the pen color using HTML color notation."""
|
|
271
|
+
if color.__class__ in [int, float]:
|
|
272
|
+
color = self._hue_to_html(color)
|
|
273
|
+
self._fill = color
|
|
274
|
+
if color is not None:
|
|
275
|
+
self._canvas[1].fill_style = color
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def width(self) -> int:
|
|
279
|
+
"""The line thickness."""
|
|
280
|
+
return self._canvas[1].line_width
|
|
281
|
+
|
|
282
|
+
@width.setter
|
|
283
|
+
def width(self, width: int):
|
|
284
|
+
"""Set the line thickness."""
|
|
285
|
+
self._canvas[1].line_width = width
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def image(self) -> PIL.Image:
|
|
289
|
+
"""Return an image of the Canvas."""
|
|
290
|
+
return PIL.Image.fromarray(self._image)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jupyter-turtle
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Simplified Turtle graphics for the Jupyter Notebook
|
|
5
|
+
Author-email: Mike Matera <michael.matera@cabrillo.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mike-matera/jupyter-turtle
|
|
8
|
+
Project-URL: Issues, https://github.com/mike-matera/jupyter-turtle/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: ipycanvas
|
|
15
|
+
Requires-Dist: jupyterlab
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# Jupyter Turtle
|
|
19
|
+
|
|
20
|
+
This is an implementation of simplified Turtle graphics to support my [Python
|
|
21
|
+
for Everyone](https://github.com/mike-matera/python-for-everyone) course.
|
|
22
|
+
[Logo](https://en.wikipedia.org/wiki/Logo_\(programming_language\)) was my first
|
|
23
|
+
programming language. The purpose of this implementation is to give beginners a
|
|
24
|
+
visual experience to help them understand algorithmic reasoning. The module
|
|
25
|
+
contains a `Turtle` class implementation with a full API and a simplified API
|
|
26
|
+
that I use in class.
|
|
27
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/jupyter_turtle/__init__.py
|
|
5
|
+
src/jupyter_turtle/turtle.png
|
|
6
|
+
src/jupyter_turtle/turtle.py
|
|
7
|
+
src/jupyter_turtle.egg-info/PKG-INFO
|
|
8
|
+
src/jupyter_turtle.egg-info/SOURCES.txt
|
|
9
|
+
src/jupyter_turtle.egg-info/dependency_links.txt
|
|
10
|
+
src/jupyter_turtle.egg-info/requires.txt
|
|
11
|
+
src/jupyter_turtle.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jupyter_turtle
|