OpenCAL is open-source software for building and operating a Computed Axial Lithography (CAL) 3D printer — a volumetric, layer-less resin printing technique. It is designed to run headless on a Raspberry Pi 5 and is controlled through a hardware LCD/encoder interface.
This project is in active development. Feedback and contributions are very welcome.
Links:
- OpenCAL Documentation
- VAMToolbox Documentation — for generating CAL-compatible print files
- Discord Server
- Overview
- Hardware Requirements
- Quick Start — Flashing the Pre-Built Image
- Building from Source
- Configuration
- Running as a systemd service
- Usage
- Video File Naming Convention
- Contributing
- License
OpenCAL runs on a Raspberry Pi 5 and orchestrates all hardware needed for a CAL print job. All parameters are tunable via opencal/utils/config.json.
Key components:
- LCD GUI (
gui/lcd_gui.py) — a state-machine menu controller for a 20×4 I2C LCD display. Drives all user interaction via a rotary encoder: file selection, settings, manual motor/LED control, alignment, and print start/stop. - Pygame GUI (
gui/pygame_app.py) — manages the projector display for precise interactive visuals during calibration and printing. - Print Controller (
hardware/print_controller.py) — orchestrates a full print job: spins up the stepper motor, activates LEDs, plays the video viampv, and records via the camera. - Hardware Controller (
hardware/hardware_controller.py) — initializes all hardware at startup; failures are caught individually so the system continues in degraded mode rather than crashing. - Stepper Controller (
hardware/stepper/tic_usb.py) — controls the Pololu Tic T249 stepper motor controller over USB. - Projector Controller (
hardware/projector_controller.py) —mpv-based video playback with crop/zoom calibration for the print resin vial. - Camera Controller (
hardware/camera_controller.py) —picamera2-based capture and H264 video recording. config.json(opencal/utils/config.json) — single source of truth for all GPIO pins, I2C addresses, LED counts, camera type, default RPM, and projector calibration values.tic_settings.yaml(opencal/utils/tic_settings.yaml) — Pololu Tic T249 motor controller settings (current limit, step mode, acceleration) applied automatically on every startup.
GUI features:
- Print from USB — automatically reads RPM from filename if present (see naming convention)
- Manual Control — LEDs, stepper motor, image capture to USB
- Settings — alignment tool, vial width finder, calibration images, USB video save prompt
- Power Options — restart, power off, kill GUI
- About — credits
| Component | Specification |
|---|---|
| Computer | Raspberry Pi 5 (2GB+ RAM) |
| Storage | microSD card ≥ 12GB |
| Motor Controller | Pololu Tic T249 (USB connection) |
| Stepper Motor | NEMA 17 (tested with 17HE19-2004S, 2.0A rated) |
| LED Array | 8×8 SK6812 RGBW NeoPixel matrix (64 LEDs) |
| Display | 20×4 I2C LCD (PCF8574 backpack, address 0x27) |
| Encoder | Rotary encoder with push button (GPIO 5, 6, 19) |
| Camera | Raspberry Pi Camera Module 3 (IMX708) |
| Projector | Any HDMI projector (tested with NexiGo Nova Mini) |
Dependencies (installed automatically on the pre-built image):
- Pololu
ticcmd— command-line tool for the Tic motor controller pi5neo— NeoPixel/SK6812 control via SPIpicamera2/libcamera— Raspberry Pi camera stackmpv— video playback for the projectorudiskie— USB drive automounting
The easiest way to get started is to flash the pre-built Raspberry Pi 5 image directly to a microSD card.
Download: opencal_pi5.img.gz on Google Drive
Steps:
- Download
opencal_pi5.img.gzfrom the link above - Download and install Raspberry Pi Imager
- Open Raspberry Pi Imager
- Click "Choose OS" → "Use custom" → select
opencal_pi5.img.gz - Click "Choose Storage" → select your microSD card (≥ 12GB)
- Click "Write" and wait for it to complete
- Insert the SD card into your Raspberry Pi 5 and power on
The system will boot directly into OpenCAL. Default login credentials:
- Username:
opencal - Password: Set on first login (you will be prompted to change it)
Note: The pre-built image already has the systemd service enabled and all hardware configured — you do not need to run any additional setup commands. The only things intentionally cleared from the image for security are WiFi credentials and SSH host keys (regenerated automatically on first boot).
WiFi setup: Place a wifi.txt file in the boot partition with the contents:
SSID=YourNetworkName
PASS=YourPassword
Prerequisites: Raspberry Pi 5 running Raspberry Pi OS Bookworm
# Clone the repo
git clone https://github.com/computed-axial-lithography/OpenCAL.git
cd OpenCAL
# Install Python dependencies
pip install -r requirements.txt --break-system-packages
# Install Pololu Tic software (ticcmd)
# Download the ARM64 package from https://www.pololu.com/docs/0J71/3.2
# and install it with: sudo dpkg -i pololu-tic-*.deb
# Install udiskie for USB automounting
sudo apt install udiskieEnable the user systemd service to start on boot:
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/opencal.path << 'EOF'
[Unit]
Description=Start OpenCAL when Wayland compositor is ready
[Path]
PathExists=%t/wayland-0
[Install]
WantedBy=default.target
EOF
cat > ~/.config/systemd/user/opencal.service << 'EOF'
[Unit]
Description=OpenCAL Printer Controller
[Service]
Type=simple
WorkingDirectory=/home/opencal/OpenCAL
ExecStart=/usr/bin/python3 -m opencal
Environment=SDL_VIDEODRIVER=wayland
Environment=WAYLAND_DISPLAY=wayland-0
Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=DISPLAY=:0
StandardOutput=journal
StandardError=journal
Restart=on-failure
RestartSec=5
TimeoutStopSec=10
EOF
systemctl --user enable opencal.path
systemctl --user start opencal.path
loginctl enable-linger opencalEdit opencal/utils/config.json to match your hardware:
{
"stepper_motor": {
"driver_mode": "tic_usb",
"A_pin": 12,
"B_pin": 13,
"default_rpm": 9,
"default_direction": "CW",
"steps_per_revolution": 3200
},
"rotary_encoder": {
"clk_pin": 5,
"dt_pin": 6,
"btn_pin": 19
}
}Motor controller settings are stored in opencal/utils/tic_settings.yaml and are automatically written to the Tic on every startup — no manual configuration of the Tic is needed.
OpenCAL uses a user-level systemd path unit that watches for the Wayland display socket and starts the app automatically. See the setup commands in Building from Source above.
To check status or view logs:
systemctl --user status opencal.service
journalctl --user -u opencal.service -fTo temporarily stop the app (e.g. for development):
systemctl --user disable opencal.path
systemctl --user kill -s SIGKILL opencal.serviceRe-enable with:
systemctl --user enable opencal.pathAll interaction is through the rotary encoder on the LCD display:
- Rotate — scroll through menu items or adjust a value
- Click (press) — select an item, confirm a value, or exit a mode
> Print from USB
Manual Control
Settings
Power Options
About
Lists all .mp4 files found on the inserted USB drive. Selecting a file:
- If the filename encodes an RPM (e.g.
part_9rpm.mp4), the motor speed is pre-set automatically. - A RPM adjustment screen appears — rotate to change, click to confirm and start the print.
- The projector displays a black image while in this menu — this is intentional to avoid accidentally curing resin while browsing.
- A Print Status screen shows while the print is running. Click to stop.
- If USB video prompt is enabled (see Settings), you will be asked whether to save the camera recording to USB after stopping.
Note: Files with "recording" in the filename are hidden from this list (they are camera output files). Avoid using "recording" in your print filenames.
Recording format: The camera saves recordings as
.h264(raw H.264 bitstream), not.mp4. To convert for playback on a computer:ffmpeg -i recording.h264 -c copy recording.mp4
Direct hardware control without running a full print job.
| Item | Action |
|---|---|
| Turn on LEDs | Turns the LED array on to the default red colour |
| Turn off LEDs | Turns the LED array off |
| Start stepper | Starts the stepper motor rotating (uses last-set RPM) |
| Stop stepper | Stops the stepper motor |
| Capture image | Takes a still image with the camera. Saved to USB with a timestamp filename if a USB drive is mounted, otherwise saved locally. Displays "Image Captured" or "Image error" on the LCD. |
| Item | Action |
|---|---|
| Calibration Images | Browse and display calibration images from the opencal/utils/calibration/ directory on the projector. |
| Show Alignment | Displays the cross-strut alignment tool image on the projector. Rotate the encoder to shift the image up/down for transverse alignment. Click to return. |
| USB video prompt | Toggles whether you are asked to save the camera recording to USB after each print. Displays current state ("USB prompt: On/Off"). |
| Find Vial Width | Opens an interactive projector display showing a white vertical bar. Rotate the encoder to adjust the bar width to match your resin vial diameter. The pixel width is shown as an overlay. Click to confirm and save the value. |
| Item | Action |
|---|---|
| Kill GUI | Stops the OpenCAL application without rebooting |
| Restart | Reboots the Raspberry Pi |
| Power Off | Shuts down the Raspberry Pi |
Displays an animated scrolling credits screen with the project contributors. The LED array runs a blue/gold checkerboard animation while credits are shown. Click to exit at any time.
OpenCAL reads the RPM value directly from the video filename. Name your files as:
<part_name>_<rpm>rpm.mp4
Examples:
cylinder_9rpm.mp4→ motor set to 9 RPM automaticallypart_v2_12rpm.mp4→ motor set to 12 RPM automatically
If no RPM is found in the filename, the menu opens with the last-used RPM value.
Contributions are very welcome! Whether it's opening issues, reporting bugs, or submitting pull requests — all of it helps.
Guidelines:
- Keep code modularized and flexible via configuration (
config.json) rather than hardcoded values. - Follow the existing module structure — hardware drivers live in
hardware/, GUI logic ingui/, utilities inutils/. - If you are unsure about a change or want to discuss a feature before building it, reach out on the Discord server.
Automated image builds are triggered by pushing a version tag of the form v... (e.g. v1.0.0). This kicks off the GitHub Actions workflow that produces a bootable Raspberry Pi 5 SD card image.
Copyright © 2026 The Regents of the University of California. All Rights Reserved.
This software is licensed under the UC Regents license — free for educational, research, and non-profit use. Commercial use requires a separate agreement with the UC Office of Technology Licensing.
See LICENSE for the full terms.