├── glider.webp
├── horn.webp
├── wave.webp
├── my_square1.webp
├── my_square2.webp
├── my_square3.webp
├── my_square4.webp
├── my_square5.webp
├── snowflake.webp
├── four-corners.webp
├── spiral-squares.webp
├── triangle-spiral.webp
├── readme-one-corners.webp
├── readme-two-corners.webp
├── double-spiral-squares.webp
├── random-fill-squares.webp
├── readme-four-corners.webp
├── readme-three-corners.webp
├── sierpinski-triangle.webp
├── alternating-white-gray-squares.webp
├── src
└── fractalartmaker
│ ├── __main__.py
│ └── __init__.py
├── pyproject.toml
├── .gitignore
└── README.md
/glider.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/glider.webp
--------------------------------------------------------------------------------
/horn.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/horn.webp
--------------------------------------------------------------------------------
/wave.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/wave.webp
--------------------------------------------------------------------------------
/my_square1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/my_square1.webp
--------------------------------------------------------------------------------
/my_square2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/my_square2.webp
--------------------------------------------------------------------------------
/my_square3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/my_square3.webp
--------------------------------------------------------------------------------
/my_square4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/my_square4.webp
--------------------------------------------------------------------------------
/my_square5.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/my_square5.webp
--------------------------------------------------------------------------------
/snowflake.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/snowflake.webp
--------------------------------------------------------------------------------
/four-corners.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/four-corners.webp
--------------------------------------------------------------------------------
/spiral-squares.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/spiral-squares.webp
--------------------------------------------------------------------------------
/triangle-spiral.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/triangle-spiral.webp
--------------------------------------------------------------------------------
/readme-one-corners.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/readme-one-corners.webp
--------------------------------------------------------------------------------
/readme-two-corners.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/readme-two-corners.webp
--------------------------------------------------------------------------------
/double-spiral-squares.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/double-spiral-squares.webp
--------------------------------------------------------------------------------
/random-fill-squares.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/random-fill-squares.webp
--------------------------------------------------------------------------------
/readme-four-corners.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/readme-four-corners.webp
--------------------------------------------------------------------------------
/readme-three-corners.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/readme-three-corners.webp
--------------------------------------------------------------------------------
/sierpinski-triangle.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/sierpinski-triangle.webp
--------------------------------------------------------------------------------
/alternating-white-gray-squares.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asweigart/fractalartmaker/HEAD/alternating-white-gray-squares.webp
--------------------------------------------------------------------------------
/src/fractalartmaker/__main__.py:
--------------------------------------------------------------------------------
1 | import fractalartmaker as fam
2 | import turtle
3 |
4 |
5 | def main():
6 | fam.demo_four_corners()
7 | input('Press enter for next demo...')
8 | turtle.reset()
9 |
10 | fam.demo_spiral_squares()
11 | input('Press enter for next demo...')
12 | turtle.reset()
13 |
14 | fam.demo_double_spiral_squares()
15 | input('Press enter for next demo...')
16 | turtle.reset()
17 |
18 | fam.demo_triangle_spiral()
19 | input('Press enter for next demo...')
20 | turtle.reset()
21 |
22 | fam.demo_glider()
23 | input('Press enter for next demo...')
24 | turtle.reset()
25 |
26 | fam.demo_sierpinski_triangle()
27 | input('Press enter for next demo...')
28 | turtle.reset()
29 |
30 | fam.demo_wave()
31 | input('Press enter for next demo...')
32 | turtle.reset()
33 |
34 | fam.demo_horn()
35 | input('Press enter for next demo...')
36 | turtle.reset()
37 |
38 | fam.demo_snowflake()
39 | input('Press enter for next demo...')
40 | turtle.reset()
41 |
42 |
43 | if __name__ == '__main__':
44 | main()
45 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=61.0"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "FractalArtMaker"
7 | # version is still read dynamically from src/fractalartmaker/__init__.py
8 | dynamic = ["version"]
9 | description = "A module for creating fractal art in Python's turtle module."
10 | readme = "README.md"
11 | authors = [
12 | { name = "Al Sweigart", email = "al@inventwithpython.com" },
13 | ]
14 | license = { text = "MIT" }
15 | keywords = []
16 | classifiers = [
17 | "License :: OSI Approved :: MIT License",
18 | "Programming Language :: Python",
19 | "Programming Language :: Python :: 3",
20 | "Programming Language :: Python :: 3.1",
21 | "Programming Language :: Python :: 3.2",
22 | "Programming Language :: Python :: 3.3",
23 | "Programming Language :: Python :: 3.4",
24 | "Programming Language :: Python :: 3.5",
25 | "Programming Language :: Python :: 3.6",
26 | "Programming Language :: Python :: 3.7",
27 | "Programming Language :: Python :: 3.8",
28 | "Programming Language :: Python :: 3.9",
29 | "Programming Language :: Python :: 3.10",
30 | "Programming Language :: Python :: 3.11",
31 | "Programming Language :: Python :: 3.12",
32 | "Programming Language :: Python :: 3.13",
33 | "Programming Language :: Python :: 3.14",
34 | ]
35 | dependencies = []
36 |
37 | [project.urls]
38 | Homepage = "https://github.com/asweigart/fractalartmaker"
39 |
40 | [tool.setuptools]
41 | package-dir = {"" = "src"}
42 |
43 | [tool.setuptools.packages.find]
44 | where = ["src"]
45 |
46 | [tool.setuptools.dynamic]
47 | version = {attr = "fractalartmaker.__version__"}
48 |
49 | [tool.pytest.ini_options]
50 | #testpaths = ["tests"]
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | pip-wheel-metadata/
26 | share/python-wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 | MANIFEST
31 |
32 | # PyInstaller
33 | # Usually these files are written by a python script from a template
34 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
35 | *.manifest
36 | *.spec
37 |
38 | # Installer logs
39 | pip-log.txt
40 | pip-delete-this-directory.txt
41 |
42 | # Unit test / coverage reports
43 | htmlcov/
44 | .tox/
45 | .nox/
46 | .coverage
47 | .coverage.*
48 | .cache
49 | nosetests.xml
50 | coverage.xml
51 | *.cover
52 | .hypothesis/
53 | .pytest_cache/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | .python-version
87 |
88 | # pipenv
89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
92 | # install all needed dependencies.
93 | #Pipfile.lock
94 |
95 | # celery beat schedule file
96 | celerybeat-schedule
97 |
98 | # SageMath parsed files
99 | *.sage.py
100 |
101 | # Environments
102 | .env
103 | .venv
104 | env/
105 | venv/
106 | ENV/
107 | env.bak/
108 | venv.bak/
109 |
110 | # Spyder project settings
111 | .spyderproject
112 | .spyproject
113 |
114 | # Rope project settings
115 | .ropeproject
116 |
117 | # mkdocs documentation
118 | /site
119 |
120 | # mypy
121 | .mypy_cache/
122 | .dmypy.json
123 | dmypy.json
124 |
125 | # Pyre type checker
126 | .pyre/
127 |
--------------------------------------------------------------------------------
/src/fractalartmaker/__init__.py:
--------------------------------------------------------------------------------
1 | import turtle, math, random, functools, copy
2 | from turtle import forward, backward, left, right, position, heading, goto, setx, sety, towards, setheading, penup, pendown, pensize, width, pencolor, fillcolor, begin_fill, end_fill, home, clear, reset, hideturtle, showturtle, bgcolor, tracer, exitonclick, done, fd, bk, lt, rt, pos, pd, pu, update, speed, bye
3 |
4 | __version__ = '0.3.2'
5 | __all__ = ['draw_fractal', 'square', 'triangle', 'demo_four_corners', 'demo_spiral_squares', 'demo_double_spiral_squares', 'demo_triangle_spiral', 'demo_glider', 'demo_sierpinski_triangle', 'demo_wave', 'demo_horn', 'demo_snowflake', 'forward', 'backward', 'left', 'right', 'position', 'heading', 'goto', 'setx', 'sety', 'towards', 'setheading', 'penup', 'pendown', 'pensize', 'width', 'pencolor', 'fillcolor', 'begin_fill', 'end_fill', 'home', 'clear', 'reset', 'hideturtle', 'showturtle', 'bgcolor', 'tracer', 'exitonclick', 'done', 'fd', 'bk', 'lt', 'rt', 'pos', 'pd', 'pu', 'update', 'speed', 'bye']
6 |
7 | turtle.tracer(50000, 0) # Increase the first argument to speed up the drawing.
8 | turtle.hideturtle()
9 |
10 |
11 | def has_kwargs_param(func):
12 | """Returns True if there is a **kwargs style parameter in func."""
13 | import inspect, functools
14 |
15 | # Unwrap decorated functions (functools.wraps etc.)
16 | func = inspect.unwrap(func)
17 |
18 | # Handle functools.partial by inspecting the underlying function
19 | if isinstance(func, functools.partial):
20 | func = func.func
21 |
22 | # If it's a callable instance, inspect its __call__
23 | if not inspect.isroutine(func) and hasattr(func, '__call__'):
24 | return has_kwargs_param(func.__call__)
25 |
26 | try:
27 | sig = inspect.signature(func)
28 | except (ValueError, TypeError):
29 | # Builtins or C-extensions may not have a signature; fallback to code flags
30 | code = getattr(func, '__code__', None)
31 | if code is None:
32 | return False
33 | # CO_VARKEYWORDS flag indicates presence of **kwargs (0x08)
34 | return bool(code.co_flags & 0x08)
35 |
36 | for p in sig.parameters.values():
37 | if p.kind == inspect.Parameter.VAR_KEYWORD:
38 | return True
39 | return False
40 |
41 |
42 | def square(size, **kwargs):
43 | if 'colors' in kwargs:
44 | depth = kwargs['depth']
45 | original_pen = turtle.pencolor()
46 | original_fill = turtle.fillcolor()
47 | i = depth % len(kwargs['colors'])
48 | turtle.pencolor(kwargs['colors'][i][0])
49 | turtle.fillcolor(kwargs['colors'][i][1])
50 | elif 'fill' in kwargs:
51 | original_fill = turtle.fillcolor()
52 | turtle.fillcolor(kwargs['fill'])
53 |
54 | # Move to the top-right corner before drawing:
55 | turtle.penup()
56 | turtle.forward(size // 2)
57 | turtle.left(90)
58 | turtle.forward(size // 2)
59 | turtle.left(180)
60 | turtle.pendown()
61 |
62 | # Draw a square:
63 | if 'fill' in kwargs or 'colors' in kwargs:
64 | turtle.begin_fill()
65 | for i in range(4): # Draw four lines.
66 | turtle.forward(size)
67 | turtle.right(90)
68 | if 'fill' in kwargs or 'colors' in kwargs:
69 | turtle.end_fill()
70 |
71 | if 'colors' in kwargs:
72 | turtle.pencolor(original_pen)
73 | turtle.fillcolor(original_fill)
74 | elif 'fill' in kwargs:
75 | turtle.fillcolor(original_fill)
76 |
77 |
78 | def basic_square(size):
79 | # Move to the top-right corner before drawing:
80 | turtle.penup()
81 | turtle.forward(size // 2)
82 | turtle.left(90)
83 | turtle.forward(size // 2)
84 | turtle.left(180)
85 | turtle.pendown()
86 |
87 | # Draw a square:
88 | for i in range(4): # Draw four lines.
89 | turtle.forward(size)
90 | turtle.right(90)
91 |
92 |
93 |
94 | def triangle(size, **kwargs):
95 | if 'colors' in kwargs:
96 | depth = kwargs['depth']
97 | original_pen = turtle.pencolor()
98 | original_fill = turtle.fillcolor()
99 | i = depth % len(kwargs['colors'])
100 | turtle.pencolor(kwargs['colors'][i][0])
101 | turtle.fillcolor(kwargs['colors'][i][1])
102 | elif 'fill' in kwargs:
103 | original_fill = turtle.fillcolor()
104 | turtle.fillcolor(kwargs['fill'])
105 |
106 | # Move the turtle to the top of the equilateral triangle:
107 | height = size * math.sqrt(3) / 2
108 | turtle.penup()
109 | turtle.left(90) # Turn to face upwards.
110 | turtle.forward(height * (2/3)) # Move to the top corner.
111 | turtle.right(150) # Turn to face the bottom-right corner.
112 | turtle.pendown()
113 |
114 | # Draw the three sides of the triangle:
115 | if 'fill' in kwargs or 'colors' in kwargs:
116 | turtle.begin_fill()
117 | for i in range(3):
118 | turtle.forward(size)
119 | turtle.right(120)
120 | if 'fill' in kwargs or 'colors' in kwargs:
121 | turtle.end_fill()
122 |
123 | if 'colors' in kwargs:
124 | turtle.pencolor(original_pen)
125 | turtle.fillcolor(original_fill)
126 | elif 'fill' in kwargs:
127 | turtle.fillcolor(original_fill)
128 |
129 |
130 | def draw_fractal(shape_drawing_function, size, specs, max_depth=8, _depth=0, reset=True, **kwargs):
131 | # BASE CASE
132 | if _depth > max_depth or size < 1:
133 | return
134 |
135 | if _depth == 0 and reset:
136 | turtle.reset()
137 |
138 | if 'depth' in shape_drawing_function.__code__.co_varnames or has_kwargs_param(shape_drawing_function):
139 | # Mirror _depth in the kwargs dictionary. This is so it's available to
140 | # the drawing functions, but we need to update it at each recursive depth level.
141 | kwargs['depth'] = _depth
142 |
143 | # Save the position and heading at the start of this function call:
144 | initialX = turtle.xcor()
145 | initialY = turtle.ycor()
146 | initialHeading = turtle.heading()
147 |
148 | # Call the draw function to draw the shape:
149 | turtle.pendown()
150 | shape_drawing_function(size, **kwargs)
151 | turtle.penup()
152 |
153 | # RECURSIVE CASE
154 | for spec in specs:
155 | # Each dictionary in specs has keys 'size', 'x',
156 | # 'y', and 'angle'. The size, x, and y changes
157 | # are multiplied by the size parameter. The x change and y
158 | # change are added to the turtle's current position. The angle
159 | # change is added to the turtle's current heading.
160 | sizeCh = spec.get('size', 1.0)
161 | xCh = spec.get('x', 0.0)
162 | yCh = spec.get('y', 0.0)
163 | angleCh = spec.get('angle', 0.0)
164 |
165 | # Reset the turtle to the shape's starting point:
166 | turtle.goto(initialX, initialY)
167 | turtle.setheading(initialHeading + angleCh)
168 | turtle.forward(size * xCh)
169 | turtle.left(90)
170 | turtle.forward(size * yCh)
171 | turtle.right(90)
172 |
173 | # Make the recursive call:
174 | draw_fractal(shape_drawing_function, size * sizeCh, specs, max_depth, _depth + 1,
175 | **kwargs)
176 |
177 |
178 |
179 | def demo_four_corners(size=350, max_depth=5, **kwargs):
180 | # Four Corners:
181 | kwargs.setdefault('colors', (('black', 'white'), ('black', 'gray')))
182 | draw_fractal(square, size,
183 | [{'size': 0.5, 'x': -0.5, 'y': 0.5},
184 | {'size': 0.5, 'x': 0.5, 'y': 0.5},
185 | {'size': 0.5, 'x': -0.5, 'y': -0.5},
186 | {'size': 0.5, 'x': 0.5, 'y': -0.5}], max_depth=max_depth, **kwargs)
187 |
188 |
189 | def demo_spiral_squares(size=600, max_depth=50, **kwargs):
190 | # Spiral Squares:
191 | kwargs.setdefault('colors', (('black', 'white'), ('black', 'gray')))
192 | draw_fractal(square, size, [{'size': 0.95,
193 | 'angle': 7}], max_depth=max_depth, **kwargs)
194 |
195 |
196 | def demo_double_spiral_squares(size=600, **kwargs):
197 | # Double Spiral Squares:
198 | kwargs.setdefault('colors', (('black', 'white'), ('black', 'gray')))
199 | draw_fractal(square, 600,
200 | [{'size': 0.8, 'y': 0.1, 'angle': -10},
201 | {'size': 0.8, 'y': -0.1, 'angle': 10}], **kwargs)
202 |
203 |
204 | def demo_triangle_spiral(size=20, max_depth=80, **kwargs):
205 | # Triangle Spiral:
206 | draw_fractal(triangle, size,
207 | [{'size': 1.05, 'angle': 7}], max_depth=max_depth, **kwargs)
208 |
209 |
210 | def demo_glider(size=600, **kwargs):
211 | # Conway's Game of Life Glider:
212 | kwargs.setdefault('colors', (('black', 'white'), ('black', 'gray')))
213 | draw_fractal(square, 600,
214 | [{'size': 1 / 3, 'y': 1 / 3},
215 | {'size': 1 / 3, 'x': 1 / 3},
216 | {'size': 1 / 3, 'x': 1 / 3, 'y': -1 / 3},
217 | {'size': 1 / 3, 'y': -1 / 3},
218 | {'size': 1 / 3, 'x': -1 / 3, 'y': -1 / 3}], **kwargs)
219 |
220 |
221 | def demo_sierpinski_triangle(size=600, **kwargs):
222 | # Sierpinski Triangle:
223 | draw_fractal(triangle, 600,
224 | [{'size': 0.5, 'y': math.sqrt(3) / 6, 'angle': 0},
225 | {'size': 0.5, 'y': math.sqrt(3) / 6, 'angle': 120},
226 | {'size': 0.5, 'y': math.sqrt(3) / 6, 'angle': 240}], **kwargs)
227 |
228 |
229 | def demo_wave(size=280, **kwargs):
230 | # Wave:
231 | draw_fractal(triangle, size,
232 | [{'size': 0.5, 'x': -0.5, 'y': 0.5},
233 | {'size': 0.3, 'x': 0.5, 'y': 0.5},
234 | {'size': 0.5, 'y': -0.7, 'angle': 15}], **kwargs)
235 |
236 |
237 | def demo_horn(size=100, max_depth=100, **kwargs):
238 | # Horn:
239 | kwargs.setdefault('colors', (('black', 'white'), ('black', 'gray')))
240 | draw_fractal(square, size,
241 | [{'size': 0.96, 'y': 0.5, 'angle': 11}], max_depth=max_depth, **kwargs)
242 |
243 |
244 | def demo_snowflake(size=200, **kwargs):
245 | # Snowflake:
246 | kwargs.setdefault('colors', (('black', 'white'), ('black', 'gray')))
247 | draw_fractal(square, size,
248 | [{'x': math.cos(0 * math.pi / 180),
249 | 'y': math.sin(0 * math.pi / 180), 'size': 0.4},
250 | {'x': math.cos(72 * math.pi / 180),
251 | 'y': math.sin(72 * math.pi / 180), 'size': 0.4},
252 | {'x': math.cos(144 * math.pi / 180),
253 | 'y': math.sin(144 * math.pi / 180), 'size': 0.4},
254 | {'x': math.cos(216 * math.pi / 180),
255 | 'y': math.sin(216 * math.pi / 180), 'size': 0.4},
256 | {'x': math.cos(288 * math.pi / 180),
257 | 'y': math.sin(288 * math.pi / 180), 'size': 0.4}], **kwargs)
258 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fractalartmaker
2 | A module for creating fractal art in Python's turtle module.
3 |
4 | Fractals are recursive, self-similar shapes. The `fractalartmaker` module (abbreviated as `fam`) lets you create fractals in Python's built-in `turtle` module. This module is based on the "Fractal Art Maker" chapter of my free book, [The Recursive Guide to Recursion](https://inventwithpython.com/recursion/).
5 |
6 | Quickstart
7 | ===========
8 |
9 | To install, run `pip install fractalartmaker` in the command line terminal. Run `python -m fractalartmaker` on Windows or `python3 -m fractalartmaker` on macOS to run the demo and view nine pieces of fractal art made by Al Sweigart.
10 |
11 | First, you must know a little bit of Python programming and Python's `turtle` module. [RealPython.com has a `turtle` module tutorial.](https://realpython.com/beginners-guide-python-turtle/)
12 |
13 | You can view a demo fractal by running the following code from the interactive shell (aka the REPL):
14 |
15 | >>> import fractalartmaker as fam
16 | >>> fam.demo_four_corners()
17 |
18 | NOTE: It's much easier to type if you import the `fractalartmaker` module with the name `fam`. We will do so throughout this tutorial.
19 |
20 | This draws the Four Corners fractal in a new turtle window.
21 |
22 | The main function we'll use in the `fam` module is `fam.draw_fractal()`. We'll need to pass it a *drawing function*, which is a function that takes a `size` argument and draws a simple shape. Here's the code for `my_square_drawing_function()` function that draws a square:
23 |
24 | def my_square_drawing_function(size):
25 | # Move to the top-right corner before drawing:
26 | turtle.penup()
27 | turtle.forward(size // 2)
28 | turtle.left(90)
29 | turtle.forward(size // 2)
30 | turtle.left(180)
31 | turtle.pendown()
32 |
33 | # Draw a square with sides of length `size`:
34 | for i in range(4): # Draw four lines.
35 | turtle.forward(size)
36 | turtle.right(90)
37 |
38 | On its own, this function will just draw a square of a given size. (Note that this function isn't recursive; it just draws one square.) But the `fam.draw_fractal()` function can use functions like this to create fractals.
39 |
40 | To make a fractal, call `fam.draw_fractal()` and pass it this drawing function, a starting size (let's go with `100`), and a list of *recursion specification dictionaries*. By recursion specification dictionary, I mean a list of dictionaries with (optional) keys `'size'`, `'x'`, `'y'`, and `'angle'`. I'll explain what these keys do later, but try calling this:
41 |
42 | fam.draw_fractal(my_square_drawing_function, 100, [{'size': 0.96, 'y': 0.5, 'angle': 11}])
43 |
44 |
45 |
46 | This draws a fractal similar to the `fam.demo_horn()` fractal. The `100` is the initial size for the first square to draw. The list of recursion specification dictionaries `[{'size': 0.96, 'y': 0.5, 'angle': 11}]` has one dictionary in it. This means for each shape drawn with the drawing function, we will draw one more recursive shape. The new square is drawn at 96% the original size (because the `'size'` key is `0.96`), located 50% of the size above the square (because the `'y'` key is `0.5`), after rotating it counterclockwise by 11 degrees (because the `'angle'` is set to `11`).
47 |
48 | By default, `fam.draw_fractal()` only goes 8 recursive levels deep. You can change this by passing a `max_depth` argument.
49 |
50 | fam.draw_fractal(my_square_drawing_function, 100, [{'size': 0.96, 'y': 0.5, 'angle': 11}], max_depth=50)
51 |
52 |
53 |
54 |
55 | Let's make each square draw *two* squares by putting a *second* recursion specification dictionary in the list. Because each square will draw two squares (and both of those two squares will draw two squares each, and so on and so on), be sure to set `max_depth` to something small like it's default `8`. Otherwise, the exponentially large amount of drawing will slow your program down to a crawl.
56 |
57 | Let's make the second recursion specification dictionary the same as the first, but it's `'angle'` key is `-11` instead of `11`. This will make it veer off clockwise by 11 degrees instead of the normal counterclockwise:
58 |
59 | fam.draw_fractal(my_square_drawing_function, 100, [{'size': 0.96, 'y': 0.5, 'angle': 11}, {'size': 0.96, 'y': 0.5, 'angle': -11}], max_depth=8)
60 |
61 |
62 |
63 |
64 |
65 |
66 | As you can see, it doesn't take much to fill up the window with too many shapes. The key to making aesthetically pleasing fractals is to take a light touch and spend a lot of time experimenting. For example, we could pass a list of three recursion specification dictionaries to draw squares in three of the corners of the parent square:
67 |
68 | fam.draw_fractal(my_square_drawing_function, 350,
69 | [{'size': 0.5, 'x': -0.5, 'y': 0.5},
70 | {'size': 0.5, 'x': 0.5, 'y': 0.5},
71 | {'size': 0.5, 'x': -0.5, 'y': -0.5}], max_depth=4)
72 |
73 |
74 |
75 | If we increase the `max_depth` to `10`, we can see a new pattern emerge:
76 |
77 | fam.draw_fractal(my_square_drawing_function, 350,
78 | [{'size': 0.5, 'x': -0.5, 'y': 0.5},
79 | {'size': 0.5, 'x': 0.5, 'y': 0.5},
80 | {'size': 0.5, 'x': -0.5, 'y': -0.5}], max_depth=10)
81 |
82 |
83 |
84 | The `fractalartmaker` module comes with a `fam.square` and `fam.triangle` drawing functions that you can play with. They will accept a `colors` keyword argument such as `colors=(('black', 'white'), ('black', 'gray'))`. Also take a look at the code for the demo functions inside the *\_\_init\_\_.py* file for more ideas. Check out the rest of this documentation for advanced tips. Good luck!
85 |
86 | NOTE: Calling `fam.draw_fractal()` automatically calls `turtle.reset()` to clear the window and move the turtle cursor back to 0, 0. If you don't want this behavior, pass `reset=False` to `fam.draw_fractal()`
87 |
88 | NOTE: To free you from having to import the `turtle` module yourself, you can call most `turtle` functions from the `fam` module: `fam.reset()`, `fam.update()`, and so on.
89 |
90 |
91 | Gallery of Demo Fractals
92 | =================
93 |
94 | def demo_four_corners(size=350, max_depth=5, **kwargs):
95 | # Four Corners:
96 | if 'colors' not in kwargs:
97 | kwargs['colors'] = (('black', 'white'), ('black', 'gray'))
98 | draw_fractal(square, size,
99 | [{'size': 0.5, 'x': -0.5, 'y': 0.5},
100 | {'size': 0.5, 'x': 0.5, 'y': 0.5},
101 | {'size': 0.5, 'x': -0.5, 'y': -0.5},
102 | {'size': 0.5, 'x': 0.5, 'y': -0.5}], max_depth=max_depth, **kwargs)
103 |
104 |
105 |
106 | def demo_spiral_squares(size=600, max_depth=50, **kwargs):
107 | # Spiral Squares:
108 | if 'colors' not in kwargs:
109 | kwargs['colors'] = (('black', 'white'), ('black', 'gray'))
110 | draw_fractal(square, size, [{'size': 0.95,
111 | 'angle': 7}], max_depth=max_depth, **kwargs)
112 |
113 |
114 |
115 |
116 | def demo_double_spiral_squares(size=600, **kwargs):
117 | # Double Spiral Squares:
118 | if 'colors' not in kwargs:
119 | kwargs['colors'] = (('black', 'white'), ('black', 'gray'))
120 | draw_fractal(square, 600,
121 | [{'size': 0.8, 'y': 0.1, 'angle': -10},
122 | {'size': 0.8, 'y': -0.1, 'angle': 10}], **kwargs)
123 |
124 |
125 |
126 |
127 | def demo_triangle_spiral(size=20, max_depth=80, **kwargs):
128 | # Triangle Spiral:
129 | draw_fractal(triangle, size,
130 | [{'size': 1.05, 'angle': 7}], max_depth=max_depth, **kwargs)
131 |
132 |
133 |
134 |
135 | def demo_glider(size=600, **kwargs):
136 | # Conway's Game of Life Glider:
137 | if 'colors' not in kwargs:
138 | kwargs['colors'] = (('black', 'white'), ('black', 'gray'))
139 | third = 1 / 3
140 | draw_fractal(square, 600,
141 | [{'size': third, 'y': third},
142 | {'size': third, 'x': third},
143 | {'size': third, 'x': third, 'y': -third},
144 | {'size': third, 'y': -third},
145 | {'size': third, 'x': -third, 'y': -third}], **kwargs)
146 |
147 |
148 |
149 |
150 | def demo_sierpinski_triangle(size=600, **kwargs):
151 | # Sierpinski Triangle:
152 | toMid = math.sqrt(3) / 6
153 | draw_fractal(triangle, 600,
154 | [{'size': 0.5, 'y': toMid, 'angle': 0},
155 | {'size': 0.5, 'y': toMid, 'angle': 120},
156 | {'size': 0.5, 'y': toMid, 'angle': 240}], **kwargs)
157 |
158 |
159 |
160 |
161 |
162 |
163 | def demo_wave(size=280, **kwargs):
164 | # Wave:
165 | draw_fractal(triangle, size,
166 | [{'size': 0.5, 'x': -0.5, 'y': 0.5},
167 | {'size': 0.3, 'x': 0.5, 'y': 0.5},
168 | {'size': 0.5, 'y': -0.7, 'angle': 15}], **kwargs)
169 |
170 |
171 |
172 |
173 |
174 |
175 | def demo_horn(size=100, max_depth=100, **kwargs):
176 | # Horn:
177 | if 'colors' not in kwargs:
178 | kwargs['colors'] = (('black', 'white'), ('black', 'gray'))
179 | draw_fractal(square, size,
180 | [{'size': 0.96, 'y': 0.5, 'angle': 11}], max_depth=max_depth, **kwargs)
181 |
182 |
183 |
184 |
185 |
186 | def demo_snowflake(size=200, **kwargs):
187 | # Snowflake:
188 | if 'colors' not in kwargs:
189 | kwargs['colors'] = (('black', 'white'), ('black', 'gray'))
190 | draw_fractal(square, size,
191 | [{'x': math.cos(0 * math.pi / 180),
192 | 'y': math.sin(0 * math.pi / 180), 'size': 0.4},
193 | {'x': math.cos(72 * math.pi / 180),
194 | 'y': math.sin(72 * math.pi / 180), 'size': 0.4},
195 | {'x': math.cos(144 * math.pi / 180),
196 | 'y': math.sin(144 * math.pi / 180), 'size': 0.4},
197 | {'x': math.cos(216 * math.pi / 180),
198 | 'y': math.sin(216 * math.pi / 180), 'size': 0.4},
199 | {'x': math.cos(288 * math.pi / 180),
200 | 'y': math.sin(288 * math.pi / 180), 'size': 0.4}], **kwargs)
201 |
202 |
203 |
204 |
205 |
206 | Advanced Features of FAM's Shape-Drawing Functions
207 | ==========================
208 |
209 | All shape-drawing functions are passed a `size` argument. We can make the white-and-gray alternating colors by adding the optional `depth` parameter to our drawing function. The `draw_fractal()` function will pass the recursion depth (`1` for the first depth level, `2` for the next, and so on) to the drawing function. In the following `square_alternating_white_gray()` drawing function, the fill color for the square is set to white or gray depending on the `depth` argument:
210 |
211 | def square_alternating_white_gray(size, depth):
212 | # Move to the top-right corner before drawing:
213 | turtle.penup()
214 | turtle.forward(size // 2)
215 | turtle.left(90)
216 | turtle.forward(size // 2)
217 | turtle.left(180)
218 | turtle.pendown()
219 |
220 | # Set fill color based on recursion depth level:
221 | if depth % 2 == 0:
222 | turtle.fillcolor('white')
223 | else:
224 | turtle.fillcolor('gray')
225 |
226 | # Draw a square:
227 | turtle.begin_fill()
228 | for i in range(4): # Draw four lines.
229 | turtle.forward(size)
230 | turtle.right(90)
231 | turtle.end_fill()
232 |
233 | draw_fractal(square_alternating_white_gray, 300,
234 | [{'size': 0.5, 'x': -0.5, 'y': 0.5},
235 | {'size': 0.5, 'x': 0.5, 'y': 0.5},], max_depth=5)
236 |
237 |
238 |
239 |
240 | If your drawing function doesn't have a `depth` parameter, then `draw_function()` won't pass one to it.
241 |
242 | You can also pass any custom keyword argument to `draw_fractal()`, and it will be forwarded to the drawing function. For example, I set up `square_random_fill()` draw function with a `custom_fill_colors` parameter. If you pass `custom_fill_colors=['blue', 'red', 'yellow', 'black', 'white']` to `draw_fractal()`, this list will be forwarded to the draw function. Note that if you pass a custom argument like `custom_fill_colors` to `draw_fractal()`, the drawing function must have a parameter named `custom_fill_colors`.
243 |
244 | import random
245 |
246 | def square_random_fill(size, custom_fill_colors):
247 | # Move to the top-right corner before drawing:
248 | turtle.penup()
249 | turtle.forward(size // 2)
250 | turtle.left(90)
251 | turtle.forward(size // 2)
252 | turtle.left(180)
253 | turtle.pendown()
254 |
255 | # Set fill color randomly:
256 | turtle.fillcolor(random.choice(custom_fill_colors))
257 |
258 | # Draw a square:
259 | turtle.begin_fill()
260 | for i in range(4): # Draw four lines.
261 | turtle.forward(size)
262 | turtle.right(90)
263 | turtle.end_fill()
264 |
265 | draw_fractal(square_random_fill, 300,
266 | [{'size': 0.5, 'x': -0.5, 'y': 0.5},
267 | {'size': 0.5, 'x': 0.5, 'y': 0.5},], max_depth=5, custom_fill_colors=['blue', 'red', 'yellow', 'black', 'white'])
268 |
269 |
270 |
271 |
272 |
273 |
274 | Python Turtle Module Cheat Sheet
275 | ===============================
276 |
277 | import turtle
278 | turtle.forward(100) # Move forward 100 steps.
279 | turtle.backward(100) # Move backwards 100 steps.
280 | turtle.left(90) # Turn left/clockwise 90 degrees.
281 | turtle.right(90) # Turn right/counterclockwise 90 degrees.
282 |
283 | turtle.position() # Return (0, 0), the current XY position of the turtle.
284 | turtle.heading() # Return 0.0, the current heading/direction of the turtle. (0 is right, 90 is up, 180 is left, 270 is down)
285 |
286 | turtle.goto(30, 25) # Move turtle to X of 30 and Y of 25.
287 | turtle.setx(30) # Move turtle left/right to X of 30 and current Y coordinate.
288 | turtle.sety(25) # Move turtle up/down to Y of 25 and current X coordinate.
289 | turtle.towards(30, 25) # Return degrees to turn left to face XY 30, 25 from current position/heading.
290 | turtle.setheading(90) # Make the turtle face up (90 degrees).
291 |
292 | turtle.penup() # "Raise the pen" and stop drawing as the turtle moves.
293 | turtle.pendown() # "Lower the pen" and start drawing as the turtle moves.
294 |
295 | turtle.pensize(4) # Set pen thickness size to 4. (Default is 1.)
296 | turtle.width() # Return 4, the current pen thickness size.
297 | turtle.pencolor('red') # Lines drawn will now be red. (Also use color formats '#FF0000' or (255, 0, 0))
298 |
299 | turtle.fillcolor('white') # Set fill color of begin_fill() and end_fill() to white.
300 | turtle.begin_fill() # Start drawing a filled-in shape.
301 | turtle.end_fill() # End drawing a filled-in shape and draw the fill color.
302 |
303 | turtle.home() # Move the turtle to 0, 0 and facing right (0 degrees).
304 | turtle.clear() # Erase all drawings on the screen, but leave the turtle in its place.
305 | turtle.reset() # Erase all drawings and move turtle to 0, 0 and facing right.
306 |
307 | turtle.hideturtle() # Don't show the turtle cursor in the window.
308 | turtle.showturtle() # Show the turtle cursor in the window.
309 |
310 | turtle.bgcolor('blue') # Make the background color of the window blue. (Default is white.)
311 |
312 | turtle.tracer(1000, 0) # Do 1000 turtle commands with 0 delay all at once. (Increase 1000 to make drawing speed faster.)
313 | turtle.update() # Call this when done to update the screen with any remaining turtle commands' drawings.
314 |
315 | turtle.exitonclick() # Close the window when the user clicks it.
316 |
317 | turtle.fd() # Same as forward()
318 | turtle.bk() # Same as backward()
319 | turtle.lt() # Same as left()
320 | turtle.rt() # Same as right()
321 |
322 | turtle.pos() # Same as position()
323 |
324 | turtle.pd() # Same as pendown()
325 | turtle.pu() # Same as penup()
326 |
327 |
--------------------------------------------------------------------------------