zach/dotter
dotter
Drive a Bantam Tools NextDraw from the browser over USB.
dotter is a JavaScript port of the NextDraw Python API (v1.7.5) that talks to the plotter's EiBotBoard (EBB) over WebSerial, plus a static page for plotting SVGs and jogging the machine. It works in Chrome, Edge and Opera on desktop. There is no server and there are no runtime dependencies.
Using the page
bun run dev
Then open http://localhost:3000, click Connect NextDraw, and pick the plotter. (Or click Use simulator to try everything with no hardware.)
- Plot an SVG: choose a file. dotter flattens it to paths, then dry-runs the whole plot on the simulator to estimate time and check safety. Plot stays disabled if the dry run finds a problem. Convert text to paths first (Inkscape: Path → Object to Path).
- Pen & motors: pen up/down, cycle, walk home, re-home, motors off.
- Jog: pen-up moves in 2.5, 12.7 or 25.4 mm steps.
- Settings: model, handling mode, speeds, pen heights and rates (the same options as the Python API). The page saves them in the browser.
Deploying
bun run build
dist/ is a static site (index.html, app.js, style.css). Serve it from any static host, e.g. dotter.zdgeier.com. WebSerial needs HTTPS (or localhost).
The library
import { NextDraw, WebSerialTransport } from './src/index.js';
const nd = new NextDraw({ transport: await WebSerialTransport.request() }); // call from a click handler
nd.interactive();
nd.options.units = 2; // mm
if (await nd.connect()) { // configures the pen and homes the machine
await nd.moveto(20, 20);
await nd.lineto(80, 20);
await nd.drawPath([[20, 40], [80, 40], [50, 70], [20, 40]]);
await nd.moveto(0, 0);
await nd.disconnect();
}
The Python names map to camelCase async methods: connect, disconnect, update, goto, moveto, lineto, go, move, line, penup, pendown, drawPath, delay, block, currentPos, turtlePos, currentPen, turtlePen, usbCommand, usbQuery. Options use camelCase too (speedPendown, penPosDown, handling, model, homing, units, ...), with the same ranges and defaults as the Python API. Additions: plotPolylines(polylines) (Plot-context equivalent: plot, then return home), walkHome, rehome, cyclePen, disableXY, and requestPause() to stop a plot (pen raised immediately).
src/svg.js turns an SVG document into inch polylines (svgToPolylines).
Where it differs from the Python API
These are deliberate. The first two exist because the simulator showed real risks.
- Step-rate margin. At 100% speed in the high-resolution handling modes, a 45° move puts the whole move on one motor right at the firmware's 25 kHz limit, and S-curve rounding overshoots the EBB's 32-bit rate register (2147548673 > 2³¹−1). dotter caps XY speed at 99.5% of that motor limit. It also checks every T3/TD command's exact peak rate before sending, and refuses to send any command that exceeds the limit (stop code 107, pen raised).
update()re-homes after a resolution change. Changing the handling mode between high and low resolution re-enables the motors, which zeroes the EBB step counters. The Python API leaves that to the user. dotter re-homes on auto-homing models.- Changing
modelre-applies the handling parameters (jerk derating), instead of keeping the previous model's values until the handling mode changes. - A polyline that collapses to one usable segment after dropping sub-step vertices is drawn. The Python driver drops it silently.
- Plot-context features that need the SVG pipeline (layers, reordering, hidden-line removal, resume, webhooks) aren't ported.
plotPolylinesplussvg.jscover plain path plotting.
Tests
bun test
The test harness is built around src/sim/ebb-sim.js: a simulated NextDraw that implements the EBB v3 serial protocol and a physical model of the carriage. That model covers mixed motor axes, travel walls, the limit switch on B1, freewheeling disabled motors (which homing relies on), and the firmware's limit-stop. It executes T3/TD moves tick by tick at 25 kHz, as the firmware does. The real driver runs against it unchanged, and the simulator flags anything unsafe:
- driving the carriage past its travel area
- more than one step per 40 µs tick on a motor (the firmware limit)
- motion sent while a limit-switch stop is active
- malformed or unknown commands
The suites cover:
- calc: the BigInt port of the firmware motion math, checked against an independent tick-by-tick accumulator model on random moves.
- protocol: the exact connect command sequence, reconnect without re-homing, firmware version and device checks, EBB errors and timeouts (after the first error, nothing more is sent).
- models: per-model and per-handling parameters, servo PWM values and pen-lift timings.
- motion: S-curve math and segment planning in all four handling modes.
- safety: homing from 10 start positions, plus failure without a limit switch or without power. Interactive moves including out-of-bounds targets, a random stress session, and drawn ink matching requested geometry (within 0.002–0.003 in). Peak speeds measured from step rates, per handling mode and speed setting. Stopping via software, the PRG button, power loss and USB unplug. The driver's tracked position is checked to match the simulated carriage exactly.
- golden: the same command stream as the official Python API (see below), and every golden scenario run against the simulator.
Golden fixtures from the official Python API
scripts/golden/gen_golden.py runs the scenarios in test/golden/scenarios.json through the real nextdraw package against a fake serial port, and records every command sent. With fixtures present, bun test requires dotter to send exactly the same commands (status polls excluded).
python3 -m venv .venv
.venv/bin/pip install https://software-download.bantamtools.com/nd/api/nextdraw_api.zip
.venv/bin/python scripts/golden/gen_golden.py
bun test
Without fixtures, those comparisons are skipped.
License
GPL-2.0-or-later. dotter is a derivative of the NextDraw software by Windell H. Oskay / Bantam Tools (GPL-2.0-or-later) and of plotink (MIT).