Project Goals
3 minBy the end of this project you can:
- Combine polygon loops, rosettes, colours, and an outer rotation loop into one composition.
- Take user input safely with the helpers from PY-L2-27.
- Pick from a palette based on the user's seed.
- Save the final drawing to a
.epsfile usingcanvas.postscript().
What's a Mandala?
5 minA mandala is a circular, symmetric pattern — same shape repeated equally around a centre. Buddhist and Hindu art has been making them for thousands of years; today turtle makes them in 30 lines.
The mandala recipe in code:
- Pick a number of petals.
- Pick a shape to repeat (polygon, star, spiral).
- Pick a colour cycle.
- Wrap the shape in a loop that rotates by 360 / petals between calls.
You've seen all four ingredients in PY-L2-34 and -35. This project bolts them together with user input and a save-to-file button.
Task 1 · The Petal Function
10 minDefine a function petal(t, sides, length) that draws one polygon. Test it on its own.
import turtle as t def petal(sides, length): for _ in range(sides): t.forward(length) t.right(360 / sides) petal(6, 50) t.done()
One hexagon. Easy. Now we'll arrange many around the centre.
Task 2 · The Mandala Function
10 mindef mandala(petals, sides, length, palette): for i in range(petals): t.fillcolor(palette[i % len(palette)]) t.begin_fill() petal(sides, length) t.end_fill() t.right(360 / petals) palette = ["crimson", "orange", "gold", "limegreen", "skyblue", "purple"] mandala(petals=24, sides=5, length=80, palette=palette) t.done()
Four parameters, infinite variations. Tune them: try petals=12 sides=3, then petals=36 sides=8. Each combination produces a different mandala.
Task 3 · The User Interface
10 minAsk the user for three numbers before drawing. Use turtle.numinput — a built-in popup that returns a number. Falls back to input() if you prefer the console.
screen = turtle.Screen() screen.setup(width=700, height=700) screen.bgcolor("black") petals = int(screen.numinput("Petals", "How many petals? (6-48)", 24, 6, 48)) sides = int(screen.numinput("Sides", "Sides per petal? (3-12)", 5, 3, 12)) length = int(screen.numinput("Size", "Petal length? (30-100)", 70, 30, 100))
numinput arguments: title, prompt, default value, min, max. The min/max are enforced by the dialog itself — no try/except needed.
Task 4 · The Palette Picker
10 minDon't hard-code a single palette — keep three and let the user pick one with another popup.
PALETTES = { "rainbow": ["red", "orange", "gold", "limegreen", "skyblue", "purple"], "warm": ["maroon", "crimson", "tomato", "darkorange", "gold"], "cool": ["midnightblue", "royalblue", "teal", "mediumseagreen", "violet"], "pastel": ["lightcoral", "peachpuff", "lemonchiffon", "palegreen", "powderblue", "lavender"], } choice = screen.textinput("Palette", "Pick: rainbow / warm / cool / pastel") or "rainbow" palette = PALETTES.get(choice.lower(), PALETTES["rainbow"])
screen.textinput returns a string (or None if the user cancels). The or "rainbow" trick handles the cancel case — Python's "or" returns the first truthy thing, so None becomes "rainbow". .get(choice.lower(), PALETTES["rainbow"]) also handles typos gracefully.
Task 5 · Save the Result
5 minAfter drawing, save the screen to a PostScript file using canvas.postscript():
from datetime import datetime filename = f"mandala_{datetime.now().strftime('%Y%m%d_%H%M%S')}.eps" screen.getcanvas().postscript(file=filename) print(f"Saved → {filename}")
.eps is Encapsulated PostScript — vector graphics. Open it with any modern viewer or convert to PNG with ImageMagick. The filename includes a timestamp so each save creates a new file rather than overwriting.
Putting It All Together · mandala.py
8 minAssemble all five tasks into one file. After the drawing finishes, prompt to either save and quit, or just quit.
Show one complete solution
# mandala.py — turtle mandala generator import turtle from datetime import datetime PALETTES = { "rainbow": ["red", "orange", "gold", "limegreen", "skyblue", "purple"], "warm": ["maroon", "crimson", "tomato", "darkorange", "gold"], "cool": ["midnightblue", "royalblue", "teal", "mediumseagreen", "violet"], "pastel": ["lightcoral", "peachpuff", "lemonchiffon", "palegreen", "powderblue", "lavender"], } screen = turtle.Screen() screen.setup(width=700, height=700) screen.bgcolor("black") screen.title("Mandala Generator") t = turtle.Turtle() t.speed(0); t.hideturtle() petals = int(screen.numinput("Petals", "How many petals? (6-48)", 24, 6, 48)) sides = int(screen.numinput("Sides", "Sides per petal? (3-12)", 5, 3, 12)) length = int(screen.numinput("Size", "Petal length? (30-100)", 70, 30, 100)) choice = (screen.textinput("Palette", "rainbow / warm / cool / pastel") or "rainbow").lower() palette = PALETTES.get(choice, PALETTES["rainbow"]) def petal(sides, length): for _ in range(sides): t.forward(length); t.right(360 / sides) t.color("white") # pen outline colour for i in range(petals): t.fillcolor(palette[i % len(palette)]) t.begin_fill() petal(sides, length) t.end_fill() t.right(360 / petals) t.hideturtle() save = screen.textinput("Save?", "Type 'y' to save the drawing.") if (save or "").lower() == "y": fname = f"mandala_{datetime.now().strftime('%Y%m%d_%H%M%S')}.eps" screen.getcanvas().postscript(file=fname) print(f"Saved → {fname}") turtle.done()
Non-negotiables: four user inputs (3 numbers + 1 palette), reusable petal + main rosette loop, save prompt at the end with a timestamped filename. Every Level-2 technique is here — functions, dicts, lists, comprehensions are skipped but everything else.
Recap
3 minA small project that pulls together every turtle trick. Reusable petal function. Main mandala loop with rotation. Palette cycling. User input via numinput/textinput. Timestamped save with canvas.postscript. The same shape works for any "procedural art generator" you might build — fractals, flowers, abstract patterns. Tune the knobs; save the keepers.
You've finished the turtle arc. Tomorrow we shift to a new game — Tic-Tac-Toe — which uses text-only input but introduces the 2-D grid logic that powers any board game you can think of.
Homework
4 minCustomise the mandala generator with two of these features:
- Layered. Draw three concentric mandalas with different petal counts.
- Random mode. If user types
?at any prompt, pick random values within the legal range. - Spiral mandala. Replace
petal()with a small spiral. - Animated. Use
speed(3)instead of0so the user watches the build.
Save three different mandalas you like and submit them as .eps or screenshot. Variety is the assignment.
Sample · layered mandala
# Three concentric mandalas — smallest on top, largest on bottom LAYERS = [(36, 8, 120), (24, 5, 80), (12, 3, 40)] # (petals, sides, length) for petals, sides, length in LAYERS: for i in range(petals): t.fillcolor(palette[i % len(palette)]) t.begin_fill() petal(sides, length) t.end_fill() t.right(360 / petals)
Each layer reuses the same code with different parameters — that's the value of parameterising. Save your favourites; mandalas are the kind of art that looks great printed and stuck on a wall.