DEV Community

Mate Technologies
Mate Technologies

Posted on

๐Ÿ“˜ Build a Trace Grid Puzzle Book Generator in Python (with Live Preview + PDF Export)

In this tutorial, weโ€™ll build a desktop puzzle book generator using Python.

By the end, youโ€™ll have an app that:

Generates grid-based tracing puzzles

Shows a live preview

Exports SVG pages

Automatically builds a print-ready PDF

Supports multiple puzzles per page

Is ready for KDP / Etsy style publishing

If youโ€™d rather jump straight to the finished code:

๐Ÿ‘‰ Full source on GitHub: https://github.com/rogers-cyber/TraceGridPuzzleBook

Letโ€™s build it step by step.

๐Ÿงฐ What Youโ€™ll Need

Make sure you have Python 3.10+ installed.

Install dependencies:

pip install pillow svgwrite reportlab ttkbootstrap cairosvg

Weโ€™ll use:

tkinter โ€“ desktop GUI

ttkbootstrap โ€“ modern UI theme

Pillow โ€“ image preview

svgwrite โ€“ vector puzzle export

reportlab โ€“ PDF creation

cairosvg โ€“ SVG โ†’ PNG for PDF embedding

๐Ÿงฑ Step 1 โ€” Project Skeleton

Create a new file:

tracegrid.py

Start with imports:

import sys, random
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox

from PIL import Image, ImageDraw, ImageTk
import svgwrite
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter

import ttkbootstrap as tb
from ttkbootstrap.constants import *
Enter fullscreen mode Exit fullscreen mode

Why?

tkinter โ†’ main window

Pillow โ†’ live preview image

svgwrite โ†’ scalable puzzle pages

reportlab โ†’ final PDF

ttkbootstrap โ†’ dark themed UI

๐Ÿง  Step 2 โ€” Main Application Class

Everything lives inside one class:

class TraceGridPuzzleBook:
    APP_NAME = "TraceGrid Puzzle Book Generator"
    APP_VERSION = "1.3.0"
Enter fullscreen mode Exit fullscreen mode

Define available options:

    GRID_STYLES = ["Square", "Isometric", "Triangle"]
    SYMBOL_TYPES = ["Dot", "Circle", "Square", "Mixed"]
    EXPORT_FORMATS = ["SVG", "PNG", "PDF"]
Enter fullscreen mode Exit fullscreen mode

Now initialize the window:

    def __init__(self):
        self.root = tk.Tk()
        tb.Style(theme="darkly")

        self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
        self.root.geometry("1130x620")
Enter fullscreen mode Exit fullscreen mode

๐ŸŽ› Step 3 โ€” State Variables

These store user settings:

        self.grid_size_var = tk.IntVar(value=5)
        self.symbol_var = tk.StringVar(value="Dot")
        self.path_complexity_var = tk.IntVar(value=50)

        self.symbol_color_var = tk.StringVar(value="#000000")
        self.line_color_var = tk.StringVar(value="#000000")
        self.bg_color_var = tk.StringVar(value="#ffffff")

        self.line_thickness_var = tk.IntVar(value=2)
        self.symbol_radius_var = tk.IntVar(value=8)

        self.rows_var = tk.IntVar(value=2)
        self.cols_var = tk.IntVar(value=2)
        self.pages_var = tk.IntVar(value=1)

        self.output_dir = Path.home() / "TraceGridPuzzleBooks"
Enter fullscreen mode Exit fullscreen mode

Each tk.Variable automatically syncs with UI widgets.

๐Ÿ–ผ Step 4 โ€” Building the Interface

Create the UI:

        self._build_ui()
        self._update_preview()
Enter fullscreen mode Exit fullscreen mode

Inside _build_ui() we:

Create left control panels

Add collapsible sections

Add spinboxes and dropdowns

Create a center Live Preview canvas

Add buttons on the right

Example: Grid size control:

tb.Spinbox(
    grid_body,
    from_=3,
    to=15,
    textvariable=self.grid_size_var,
    command=self._update_preview
).grid(row=0, column=1)
Enter fullscreen mode Exit fullscreen mode

Whenever the value changes, _update_preview() redraws puzzles instantly.

๐Ÿงฉ Step 5 โ€” Generating a Single Puzzle Path

Each puzzle is just a random walk through a grid:

def _generate_single_puzzle(self):
    size = self.grid_size_var.get()
    complexity = self.path_complexity_var.get() / 100

    total_steps = max(2, int(size * size * complexity))

    path = [(0, 0)]
    visited = set(path)

    for _ in range(total_steps - 1):
        x, y = path[-1]

        neighbors = [
            (x+dx, y+dy)
            for dx, dy in [(0,1),(1,0),(0,-1),(-1,0)]
            if 0 <= x+dx < size
            and 0 <= y+dy < size
            and (x+dx, y+dy) not in visited
        ]

        if not neighbors:
            break

        next_cell = random.choice(neighbors)
        visited.add(next_cell)
        path.append(next_cell)

    return path
Enter fullscreen mode Exit fullscreen mode

Whatโ€™s happening?

Start at (0,0)

Randomly move up/down/left/right

Never revisit a cell

Stop when complexity limit is reached

This produces clean tracing paths.

โœ๏ธ Step 6 โ€” Drawing Puzzles (PNG Preview)

For live preview we draw with Pillow:

def _draw_puzzle_png(self, draw, path, x_offset, y_offset, cell_width, cell_height):
    step = cell_width / (self.grid_size_var.get()+1)
    radius = self.symbol_radius_var.get()

    for i, (x, y) in enumerate(path):
        px = x_offset + (x+1)*step
        py = y_offset + (y+1)*step

        draw.ellipse([px-radius, py-radius, px+radius, py+radius], fill=self.symbol_color_var.get())

        if i > 0:
            prev = path[i-1]
            px0 = x_offset + (prev[0]+1)*step
            py0 = y_offset + (prev[1]+1)*step

            draw.line([px0, py0, px, py],
                      fill=self.line_color_var.get(),
                      width=self.line_thickness_var.get())
Enter fullscreen mode Exit fullscreen mode

Dots + connecting lines = tracing puzzle.

๐Ÿ”„ Step 7 โ€” Live Preview

Every UI change regenerates sample puzzles:

def _update_preview(self):
    img = Image.new("RGB", (400,400), self.bg_color_var.get())
    draw = ImageDraw.Draw(img)

    for r in range(self.rows_var.get()):
        for c in range(self.cols_var.get()):
            path = self._generate_single_puzzle()
            self._draw_puzzle_png(draw, path, c*200, r*200, 200, 200)

    self.preview_image = ImageTk.PhotoImage(img)
    self.preview_canvas.delete("all")
    self.preview_canvas.create_image(0,0, anchor="nw", image=self.preview_image)
Enter fullscreen mode Exit fullscreen mode

This gives instant visual feedback.

๐Ÿ“„ Step 8 โ€” Exporting SVG Pages

Each page is a large SVG with multiple puzzles:

def _export_page_svg(self, filename, puzzle_paths, rows, cols):
    dwg = svgwrite.Drawing(filename, size=(1000,1000))

    cell_w = 1000 / cols
    cell_h = 1000 / rows

    for i, path in enumerate(puzzle_paths):
        r = i // cols
        c = i % cols

        self._draw_puzzle_svg(
            dwg,
            path,
            c*cell_w,
            r*cell_h,
            cell_w,
            cell_h
        )

    dwg.save()
Enter fullscreen mode Exit fullscreen mode

SVG is perfect for printing because itโ€™s vector-based.

๐Ÿ“š Step 9 โ€” Building the PDF

We convert SVG โ†’ PNG โ†’ PDF:

def _export_pdf(self, pdf_filename, svg_files):
    c = canvas.Canvas(str(pdf_filename), pagesize=letter)

    for svg in svg_files:
        from cairosvg import svg2png

        png = svg.with_suffix(".png")
        svg2png(url=str(svg), write_to=str(png))

        img = Image.open(png)
        c.drawInlineImage(img, 0, 0)
        c.showPage()

    c.save()
Enter fullscreen mode Exit fullscreen mode

Now you have a multi-page puzzle book PDF.

โ–ถ๏ธ Step 10 โ€” Run the App

Finally:

if __name__ == "__main__":
    TraceGridPuzzleBook().run()
Enter fullscreen mode Exit fullscreen mode

Run it:

python tracegrid.py

โœ… What You Now Have

A full desktop tool that can:

Generate tracing puzzles

Preview layouts live

Batch-create pages

Export SVG

Automatically produce PDF books

Support multiple puzzles per page

Customize colors, sizes, and complexity

Perfect for:

Activity books

KDP publishing

Etsy printables

Worksheets

Rapid puzzle prototyping

๐Ÿš€ Full Source Code

๐Ÿ‘‰ https://github.com/rogers-cyber/TraceGridPuzzleBook

Feel free to fork it, improve it, or build your own puzzle generators on top.

Top comments (0)