← Back to projects
Egg Lathe

Egg Lathe

An automated CNC-style lathe for decorating Ukrainian pysanky eggs, combining custom hardware, firmware, and a desktop controller app. Built for makers and digital artists, it turns parametric patterns and single-line image algorithms into precise motion commands over serial to an STM32-based motion system.

Production Updated 10/12/2025
ccpppythonshellstm32embeddedsimulation

Overview

Egg-Lathe is my end-to-end attempt to automate Ukrainian egg dying (Pysanky) using a custom-built CNC-style lathe. The project spans hardware design, embedded firmware, and a desktop control application that can both simulate and drive the physical machine to draw programmable patterns around an egg.

Role & Context

I built this as a personal side project to explore the full stack of a mechatronics system:

  • Mechanical design in CAD and laser-cut parts
  • Embedded control on a microcontroller-based lathe
  • A host-side Python application with a GUI, pattern generation, simulation, and serial control

I owned the design and implementation across hardware, firmware, and software.

Tech Stack

  • C / C++ (embedded firmware, Arduino/STM32-style code)
  • Python (control application, pattern generation, simulation, tooling)
  • Shell (batch execution scripts and helpers)
  • PyQt5 (desktop GUI)
  • PySerial (serial communication with the lathe)
  • NumPy, Pillow, Matplotlib, SciPy (image and data processing, visualization)
  • Turtle / Pygame (2D preview / simulation experiments)
  • Fusion 360 (mechanical CAD)
  • SVG toolchain + laser cutting (hardware fabrication)

Problem

Traditional Pysanky decoration is slow, highly manual, and difficult to reproduce exactly, especially for complex, banded, or algorithmic patterns. I wanted a system that could:

  • Hold and rotate an egg precisely
  • Move a tool linearly along its surface
  • Accept higher-level “patterns” (sine bands, grids, zig-zags, etc.)
  • Simulate designs before committing them to a fragile egg
  • Be extendable toward image-based, single-line art around the egg

The challenge was turning high-level pattern descriptions into robust motion control on inexpensive hobby-grade hardware.

Approach / Architecture

I split the system into three main layers:

  1. Hardware (Lathe)

    • Custom laser-cut frame with chucks, bumpers, and slides for the egg and tool.
    • Stepper-driven rotation (egg) and linear axis (tool), designed in Fusion 360 and exported to SVG for laser cutting.
  2. Firmware (Microcontroller)

    • A small command protocol over serial with explicit prefixes and completion markers.
    • Commands like calibration, X/Y movement, and wait/break-wait, abstracted behind short, parseable tokens.
  3. Desktop Application (Host)

    • A Python application that:
      • Generates pattern commands as high-level objects (SineBand, ZigZagBand, Circle, Band, etc.).
      • Compiles them into low-level serial commands using a Command + Queue abstraction.
      • Provides a PyQt GUI for connection, movement control, pattern execution, and simulation/preview.

Around this, I experimented with image-based pattern generation (stippling, Bezier-curved single-line drawings) that could eventually map 2D art onto the egg surface.

Key Features

  • Programmable pattern primitives (SineBand, ZigZagBand, Circle, Band) that compose into complex designs.
  • Serial command abstraction (Command/Queue) with automatic position tracking and layering.
  • PyQt-based control UI with movement controls, connect/simulate buttons, and a preview area.
  • Simulation of motion paths using Turtle/Pygame and on-screen previews before running on hardware.
  • Hardware design exported from Fusion 360 directly to laser-cuttable SVG parts.
  • Serial monitoring and plotting tools for debugging motion and sensor data.
  • Experimental image-to-path tooling (stippling + Bezier curves and binary-to-image encoders).

Technical Details

Command Protocol and Firmware Interface

On the firmware side, I defined a very small, string-based protocol in CommandMapping.h:

  • Prefixes such as:

    • C- – command prefix
    • R- – response
    • S- – status
    • C-MX, C-MY – move X / Y
    • C-CB – calibrate (CalibratePrefix = "CB")
  • A completion token -CC signals that the microcontroller finished executing a command batch.

On the host side, the Command class in structure.py encodes these:

if self.type == "move":
    movement = (round(value[0]), round(value[1]))
    self.commands.append(f"C-MX {movement[0]}")
    self.commands.append(f"C-MY {movement[1]}")
elif self.type in ["cal", "calibrate"]:
    self.commands.append("C-CB")

The execute method sends each underlying string and blocks until it reads -CC or a timeout:

ser.write((command + "\n").encode())
...
if response == "-CC":
    return response

This keeps the host and device synchronized without needing complex state machines.

Motion Planning and Pattern Generation

Pattern primitives live in Production/Lathe Application/commands.py and generate lists of Command objects, not raw strings:

  • SineBand: Wraps a cosine-based wave around the egg; each step increments the Y axis while modulating X:

    for i in range(0, steps):
        queue.append(Command("move", (math.cos(i/200*frequency*2*math.pi)*amplitude/4, 1), layer))
  • Circle: Builds a closed loop on the surface by integrating the derivative of a circle’s parametric equation, while tracking floating-point overflow and correcting for rounding:

    • circleCoordinates(angle) computes incremental motion as (-sin(angle) * mult, cos(angle) * mult).
    • Each step accumulates overflow in movement_overflow and only sends rounded movements when non-zero.
    • At the end, it issues a correction move so the net displacement closes the loop.
  • ZigZagBand: Alternates direction across a band, accumulating sub-step movement and emitting commands only when rounding yields a non-zero integer step to avoid jitter.

  • Band: Produces a rectangular band via repeated Y sweeps and X increments, then returns to the origin.

The Queue class is responsible for:

  • Flattening nested lists of commands
  • Assigning layers (for future interleaving/visualization)
  • Tracking an approximate position by summing move values
  • Iterating across commands to execute them over serial

GUI and Simulation

In Production/Lathe Application/application.py I built a PyQt MainWindow that:

  • Accepts a pre-built Queue of commands.
  • Provides:
    • A movement control panel (for manual jogging and calibration).
    • Buttons for connecting to the serial port and for starting a simulation.
    • A preview area (QLabel) where I can draw or load an image representing the current pattern.
  • Uses a QTimer to periodically step through the Queue in simulation mode, updating the preview and internal progress.

Earlier experiments in init test/ use PyQt and a lightweight script editor (execution.py) where I can quickly iterate on pattern scripts and send them via a SerialCommunication wrapper. These experiments informed the final “Production” structure.

For visualizing and debugging:

  • Turtle/Pygame are used in structure.py and draw_functions.py to render paths.
  • seriallisten.py logs time-stamped numeric data from the device, segments it based on inactivity, and plots segments with Matplotlib to inspect behavior over time.

Image and Data Tooling

I built a few utilities around the core system:

  • filetoimage.py: Encodes arbitrary binary files into RGB images using 3 bits per pixel (one per channel) to help visualize firmware images or other data blobs.
  • hexToRGB.py: Turns CSV data of hex codes into a spectrogram-like image for quick visual inspection of streams.

Under patterntest/Single-Line-Portrait-Drawing-master/ I integrated an existing research-style pipeline that:

  • Applies weighted Voronoi stippling to an input image.
  • Connects points via short straight segments or Bezier splines.
  • Produces single-line drawings that could be mapped to lathe movements in a future iteration.

Results

  • Built a functioning prototype egg lathe with custom laser-cut hardware, stepper-driven axes, and a consistent mechanical setup.
  • Implemented a stable serial protocol and host-side queueing layer that can reliably stream hundreds of movements without desynchronization.
  • Generated complex patterns (multi-band sine, zig-zag, grids, and circles) in code and rendered them on eggs through the machine.
  • Achieved interactive control and preview via a PyQt GUI, including a simulation mode that reduces trial-and-error on physical eggs.
  • Established a foundation for future work on mapping 2D images to paths on curved egg geometry.

Lessons Learned

  • Precision vs. integer hardware: Accumulating floating-point movement and only emitting integer steps (with overflow correction) is critical for closed shapes like circles.
  • Protocol simplicity pays off: A small, well-structured command set with explicit completion markers is much easier to debug than a more “feature-rich” protocol.
  • Simulate early: Having Turtle/Pygame and PyQt previews saved hardware, time, and a lot of broken eggs.
  • Layered abstractions help experimentation: Separating pattern generation from serial transport made it easy to add new patterns or swap in simulations.
  • Mechanical constraints drive software design: Backlash, step resolution, and physical clearances all fed back into how I discretized movement and tuned resolutions.

Links