Skip to main content

PlotDeck

A G-code sender and editor for small CNC routers running GRBL 1.1 on an Arduino Uno. macOS desktop app, Python + PySide6.

PlotDeck connects over USB serial, lets you edit and preview a G-code file, streams it with GRBL's character-counting flow control, and gives you manual control (jog, home, zero, overrides) with live position and toolpath feedback.

New here? Read USER_MANUAL.md, or open it inside the app from the Manual toolbar button or Help → User Manual (F1).

PlotDeck screenshot

Features

  • Connection — USB port discovery, 115200 baud default, GRBL banner detection (the Uno resets when the port opens), 5 Hz status polling, and a Demo mode that runs a simulated machine with no hardware.
  • Streaming — 127-byte character-counting sender, pause/resume, safe stop (feed hold + soft reset), start from a line, and GRBL check mode ($C) dry runs.
  • Editor — line numbers, G/M/axis/feed syntax colours, current-line and executed-line markers, error markers, find/replace, go-to-line, toggle comment, strip comments, renumber, and offset/scale/rotate transforms.
  • Visualizer — 2D toolpath with executed (green) and remaining (dashed) paths, live tool position, scroll/button zoom, pan, fit-to-view, bounding box and time estimate. Optional 3D view with orbit and zoom when PyOpenGL is installed.
  • Machine control — jog pad with step chips and keyboard control, work zeroing per axis, G54–G59 selection, homing, unlock, jog cancel, feed / rapid / spindle overrides, spindle on/off, and a Z-probe wizard.
  • Safety workflows — pre-flight summary (time, bounds, depth, tools, warnings), M6 interception with a tool-change pause, error:N pauses the job instead of blindly continuing, alarm dialog with unlock/home recovery, and automatic job-interrupted reporting on disconnect.
  • Settings — editable $$ table with descriptions, plus persistence of recent files, port, theme and window layout via QSettings.
  • Help — the full user manual is bundled and rendered in-app from the Manual toolbar button or Help → User Manual (F1), with search and links.

Requirements

  • macOS (only platform tested), Python 3.10 or newer
  • Arduino Uno with GRBL 1.1 and a CNC shield (for real hardware)
  • Python packages: PySide6, pyserial, pyqtgraph, numpy
  • Optional: PyOpenGL (3D view), PyInstaller (packaging)

Setup

bash scripts/setup_env.sh      # creates .venv and installs .[dev,3d]
source .venv/bin/activate

Or manually:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev,3d]"

Run

python -m plotdeck

Pick a /dev/cu.usbmodem* port (or /dev/cu.usbserial* for CH340 boards) and press Connect. Click Demo to explore the interface without hardware.

Typical workflow

  1. Connect and wait for the state badge to read IDLE.
  2. Home the machine, jog to the workpiece origin, and zero X/Y (and Z, or use Machine → Probe Z… with a touch plate).
  3. Open a G-code file (⌘O or drag and drop). Check the toolpath preview.
  4. Press Run. The pre-flight dialog shows the time estimate, bounding box, tools and warnings; Dry run uses GRBL check mode.
  5. Watch progress in the Job panel; the editor follows the executing line. Pause sends feed hold, Stop (⌘.) sends feed hold then soft reset.
  6. If GRBL returns error:N, the job pauses and asks whether to skip the line or abort. Insert tool changes when the M6 dialog appears.

Tool changes: PlotDeck pauses and prompts for M6, but it does not automatically stop a manually switched spindle or retract Z. Turn the spindle off and raise Z before reaching in, then re-probe Z if the tool length changed. Only an automatic spindle relay wired to GRBL can be stopped by GRBL itself.

Keyboard shortcuts

Action Shortcut
Open / Save ⌘O / ⌘S
Find / Go to line ⌘F / ⌘L
Run / Run from cursor ⌘R / ⌘⇧R
Pause / Resume toolbar (feed hold / cycle start)
Stop ⌘.
Soft reset ⌘⇧.
Toggle comment ⌘/
Connect / Disconnect ⌘K
Fit toolpath ⌘0 (or F with the preview focused)
Jog arrows (X/Y), PgUp/PgDn (Z), [ ] step, Space cancel

Jog keys only act while the jog panel has focus. The console accepts commands when idle; during a job line commands are refused (real-time commands such as ? or ! still work from a serial terminal, but the app controls them).

Tests

source .venv/bin/activate
QT_QPA_PLATFORM=offscreen pytest -q
ruff check src tests
black --check src tests
mypy

The suite drives the core against an in-process GRBL simulator (tests/fake_grbl.py) that enforces the 127-byte buffer, and includes headless UI smoke tests (pytest-qt). CI runs the same checks on macOS.

Packaging

bash packaging/build_app.sh

The built dist/PlotDeck.app was launched headlessly during development to confirm the bundle starts (PySide6, pyqtgraph and the Qt platform plugins are collected; PyOpenGL is included when installed).

This produces dist/PlotDeck.app via packaging/plotdeck.spec, and a .dmg when create-dmg is installed. For distribution outside your own Mac, sign and notarize with your Apple Developer identity:

codesign --deep --options runtime --sign "Developer ID Application: …" dist/PlotDeck.app
xcrun notarytool submit … --wait
xcrun stapler staple dist/PlotDeck.app

Architecture

src/plotdeck/
├── core/            # no Qt imports; unit-testable headless
│   ├── transport.py     # Transport ABC, SerialTransport thread, MockTransport
│   ├── grbl_protocol.py # real-time bytes, response classifier, error tables
│   ├── status.py        # <...> status reports → MachineStatus
│   ├── gcode_parser.py  # cleaning, tokenizing, toolpath + time estimate
│   ├── streamer.py      # character-counting job state machine
│   └── controller.py    # MachineController orchestration
├── ui/              # PySide6 widgets
│   ├── main_window.py   # layout, menus, shortcuts, state-driven enablement
│   ├── editor.py        # editor, gutter, highlighter, find bar
│   ├── console.py       # colour-coded console + history
│   ├── status_panel.py  # DRO, state badge, override sliders
│   ├── jog_panel.py     # jog pad, zeroing, WCS
│   ├── visualizer.py    # 2D/3D toolpath preview
│   ├── job_panel.py     # progress and timing
│   ├── dialogs.py       # pre-flight, tool change, probe, settings
│   └── theme.py         # colour tokens and QSS
└── utils/           # settings (QSettings) and logging

The UI never touches the serial port. Controller callbacks are marshalled to the main thread through a small QObject signal bridge. The controller and streamer are plain Python, so tests and CLI use do not need a Qt event loop.

GRBL notes

  • Buffer size is 127 usable bytes; lines are counted as len(line) + 1.
  • Real-time bytes (?, !, ~, 0x18, 0x85, override bytes) bypass the queue and never produce ok.
  • Manual commands are acknowledged separately from the job stream, so a $C or $H ok cannot corrupt buffer accounting.
  • M6 is never forwarded (GRBL would return error:20); the streamer pauses, the UI prompts, then the job resumes after the tool change.
  • On soft reset the streamer clears its queue and pending bytes and the app re-syncs from the next status report.

Limitations / not implemented

  • Only GRBL 1.1 is supported; Marlin/FluidNC are out of scope.
  • No CAM: PlotDeck sends G-code produced elsewhere.
  • No auto-reconnect, macros editor, tool/material library, or height-map leveling (post-1.0 in the plan).
  • The 3D view requires PyOpenGL (pip install -e ".[3d]"); without it the 3D button reports that the view is unavailable.
  • Clicking a toolpath segment does not jump to its line yet.
  • Find/replace is literal text, not regular expressions; hover documentation and G/M-code autocomplete are not implemented.
  • Continuous (hold-to-jog) motion and saved macros are not implemented.
  • The app icon is optional; drop packaging/icon.icns in place before building if you want one.

Safety

This software controls a machine that can injure you. Wear eye protection, secure the workpiece, know where the power switch is, and keep a hand near it for the first cut. A software stop is not an emergency stop — wire a hardware e-stop that cuts motor and spindle power. Test with the tool disconnected or raised before cutting material.

Metadata

Release files for plotdeck 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for plotdeck 1.0.0
File Size Uploaded Provenance
plotdeck-1.0.0.tar.gz 66.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for plotdeck 1.0.0
File Interpreter ABI Platform
plotdeck-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 132.5 kB

Release files / plotdeck-1.0.0.tar.gz

Download URL plotdeck-1.0.0.tar.gz
Size 66.7 kB
Tags Source
SHA-256 checksum
How to use checksums
a5424216289fa2200bd61b8cf57aa24794a3084d25d6db8c3a505980d90d77a2
BLAKE2b-256 checksum
How to use checksums
2473f48166beb827b0a5c98eacfed7a86b8543135fee4c26799d3c9ea5538dca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / plotdeck-1.0.0-py3-none-any.whl

Download URL plotdeck-1.0.0-py3-none-any.whl
Size 65.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c7f7afc50074a50523bca6e09f010adba59700c9c792997466bb25c21380aa23
BLAKE2b-256 checksum
How to use checksums
1b97c3c0aea1cb831ab7091e2d5af679972e8a00538e94173ee6bb02220e491c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Security for plotdeck 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for plotdeck-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page