Skip to content

sdjayna/super-duper-octo-robot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

655 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jupiter Jayna's Plotter Art Control Centre

A plotter-first generative art workstation that goes from browser math to multi-pen AxiDraw output—and now Bantam Tools NextDraw compatibility—without leaving the keyboard.

If you have an AxiDraw, a Bantam Tools NextDraw (their modern rebadge of the platform), or any plotter that can digest SVG layers and love algorithmic art, this repo gives you a complete local studio: mm-accurate previews, color-aware layering, a Python server that proxies the axicli, and a set of battle-tested drawing algorithms. The active gallery is intentionally focused on our hatching studies—perfect rectangles (Bouwkamp), photo triangle mosaics, Voronoi sketches, and implicit line walkers—while the rest of the legacy modules now live under drawings/disabled so you can re-enable them locally without letting the manifest rediscover them. Everything ships as source, so you can bend it to your own plotting rituals or hack on it live during a stream.

Bantam Tools acquired and now ships the NextDraw-branded successors to AxiDraw hardware (store, story). This project is leaning hard into that future so the tooling you install today follows you onto the new machines without any workflow change.

Table of Contents

Drawing Control view – 2114×1259 Plotter Control view – 2113×1253

Optimized for the AxiDraw SE/A3 and the Bantam Tools-branded NextDraw hardware (identical motion stack, updated vendor), but nothing prevents you from wiring in another model by editing server/plotter_config.py.

Why This Tool Exists

  • Bridges UI + hardware - front-end sliders talk to a Python server that shells out to axicli, streaming progress back over SSE, so you can iterate fast without babysitting the machine.
  • Obsessive paper + pen modeling - paper margins, nib widths, and medium presets are first-class citizens, so the SVG matches the sheet on your desk.
  • Algorithm playground - the drawing registry is tiny, modern JavaScript; adding a new tiling or curve takes one module and hot reload, and any config knobs you expose are surfaced automatically in the “Drawing Settings” panel as sliders, selects, or number inputs. Built‑in modules cover Hilbert curves, Bouwkamp tilings, phyllotaxis spirals, spirograph families, Voronoi/Lloyd sketches, calibration grids, Lissajous curves, Superformula shapes, Clifford attractors, Lorenz/Ikeda/Peter de Jong attractors, flow fields, contour maps, wave interference, circle packing, DLA clusters, Truchet tiles, sorting arcs, and Gray‑Scott (Turing) patterns.
  • Transparency-first - no cloud, no hidden binaries; everything from the Makefile to the SSE heartbeat loop is readable and hackable.
  • Future-proof hardware direction - Bantam Tools’ NextDraw machines run the same command-line toolchain as legacy AxiDraw units, so this repo treats them as first-class plotters and keeps the UI/server abstraction ready for whatever the next carriage looks like.

Hardware compatibility & project direction

  • AxiDraw SE/A3 remains the default profile (penlift 3, SE servo). All the presets in config/plotters.json are still tuned for the stock carriage and axicli workflow.
  • Bantam Tools NextDraw devices (available via the NextDraw collection) use the same CLI surface, so no code changes are required—set the penlift + travel envelope once in config/plotters.json and keep plotting. The repo now calls out this compatibility everywhere so new owners know they can stay on this stack when moving to Bantam’s hardware line. If you want the backstory, here’s Bantam’s write-up on the acquisition: “A look back: the AxiDraw story (part 2)”.
  • Direction of travel: continue building local-first tooling that speaks the common AxiDraw/NextDraw CLI, with hooks ready for Bantam firmware extensions (pen calibration, travel caps) as they ship. Expect future presets + docs to talk explicitly about both names so nobody has to guess whether this repo still works with the rebranded machines.

Feature Highlights

  • NextDraw-ready plotting stack – the Python server and preview controller already speak the axicli language Bantam retained for NextDraw, so swapping in the new hardware is literally a config change. The README, changelog, and TODO now reflect that direction so the community knows we’re marching alongside Bantam’s roadmap.

  • Skeleton hatching everywhere – The shared hatching helpers now expose a straight-skeleton/medial-axis mode (next to Serpentine/Scanline/None) so any drawing can trace a single continuous path per polygon, diving bisector spokes deep into acute corners before optionally outlining the boundary. Photo Triangles, Voronoi, and Bouwkamp already default to it for rich, corner-to-corner coverage.

  • Tabbed control console – switch between Drawing settings and Plotter controls without scrolling. Each panel keeps its state, so you can tweak a control, hop over to the plotter, and return without losing context.

  • Paper-aware preview – the merged “Paper & Margin” panel now blends the selected paper + medium metadata to set background color, simulate bleed/jitter via SVG filters, apply paper-specific texture overlays, surface warnings when a combo risks embossing or over-saturation, and (when rulers are on) outline the plotter’s maximum travel area.

  • Adaptive plotter defaults – the Plotter Control tab auto-tunes Pen Rate Lower for each paper + medium combination so acrylic markers keep moving fast while delicate stocks slow the carriage to stay safe.

  • Layer travel caps – the Plotter Control Medium panel now exposes a “Max Travel Per Layer” slider (1 m–100 m with an ∞ stop) that splits long paths/layers into multiple reload-friendly passes so ink never runs dry mid-color.

  • Calibration drawing – ship a dedicated “Calibration Patterns” preset that lays out parallel lines, rings, multi-wave bundles, arc sweeps, Bezier ribbons, radial fans, touching polygons, and serpentine-filled shapes across adjustable spacing bands (including an SE/A3 micro-spacing stress row) so you can map slider values to real AxiDraw mechanics before committing a hero plot.

  • Per-drawing UI controls – every drawing can declare sliders/selects (stroke spacing, Hilbert recursion level, etc.) via metadata, and the UI renders them automatically with persisted values.

  • Shared hatching library – fill routines (serpentine, scanline, contour, skeleton, or none) can now be selected per drawing, and the helpers accept arbitrary polygons so future drawings inherit the same ink-friendly toolpaths.

  • Layer travel optimizer – each color layer reorders its segments with a nearest-neighbor heuristic so pen-up moves shrink dramatically before the next polygon or segment starts.

  • Real-time plotting telemetry – Axidraw’s --progress stream is piped straight into the Messages panel with a live, color-coded progress bar so you can see mm/s throughput and ETA without watching the terminal.

  • Resume-safe plotting – each layer plot writes output/plot_resume.log and the Plotter panel exposes a Resume button whenever that log points to an interrupted job, so a Stop/Ctrl‑C doesn’t force you to rebuild the SVG. Starting a new layer automatically overwrites the log so “resume” always maps to the most recent plot.

  • Photo Triangles – upload any reference image and the new mosaic drawing samples it, runs a point-weighted Delaunay triangulation, then hatches each triangle (serpentine/scanline/contour/skeleton/none via the global controls) with the nearest palette color so multi-pen plots can echo real photos.

  • Log-scale sliders – controls like Hilbert’s segment size use logarithmic scaling under the hood to give fine-grained precision near zero while still allowing very large values.

  • Optimised Hilbert generator – the curve now uses an iterative, bitwise implementation that handles high recursion levels gracefully.

  • Comprehensive tests – Vitest covers the drawing registry, math helpers, and the new controls plumbing, so refactors stay safe.

What Ships in This Repo

  • Client - vanilla JS + CSS app (client/js, client/templates) with live preview, layer toggles, debug console, and mm ruler overlay.
  • Server - Python http.server wrapper (server/server.py) that serves the UI, streams /plot-progress, and runs bin/axicli commands for each layer.
  • Config data - config/papers.json and config/mediums.json describing margins, stroke widths, nib metadata, color palettes, and optional default preview colors.
  • Output pipeline - timestamped SVGs in output/ with configuration comments plus Inkscape-compatible layers ready for plotting or archival.
  • Docs + tooling - Makefile, Vitest setup, TODO/CHANGELOG/CONTRIBUTING, and a reference screenshot so people know what they’re installing.
  • Drawings - a top-level drawings/ directory split into core/ (currently the focused quartet: Bouwkamp perfect rectangle, Photo Triangles, Voronoi, and implicit line walkers), community/ (user-contributed experiments that still ship), disabled/ (archived drawings kept out of the manifest but available for local hacking), and shared/ helpers so contributions don’t need to dig through the client bundle.
    • Want one of the legacy modules (Hilbert, spirograph families, flow fields, etc.) back? Move it from drawings/disabled/<core|community> into the active folder, rebuild drawings/manifest.json, and it will reappear instantly.
├── client/
│   ├── js/…                 # Drawing registry, color utils, configs
│   ├── static/css/styles.css
│   └── templates/plotter.html
├── drawings/
│   ├── core/                # Active first-party drawing definitions
│   ├── community/           # Active user contributed drawings
│   ├── disabled/            # Archived drawings kept out of the manifest
│   ├── shared/              # Config bases + helpers + adapters
│   └── manifest.json        # Prebuilt manifest consumed by the loader
├── server/
│   ├── server.py            # HTTP + axicli bridge + SSE
│   ├── plotter_config.py    # Pen heights, penlift, model ids
│   └── server_runner.py     # Dev server with autoreload
├── config/
│   ├── papers.json          # ISO + Bristol presets w/ margins
│   └── mediums.json         # Pen brands, nib widths, stroke styles
└── Makefile / tests / docs  # Tooling, Vitest config, server docs

Quick Start (macOS, local-first)

Prerequisites

  • macOS (project currently tested on Apple Silicon)
  • Python 3.x
  • Node.js 18+
  • Git, Make, and a modern browser
  • An AxiDraw SE/A3 (recommended) or any plotter that consumes layered SVGs

Bootstrap the dev environment

make install   # virtualenv + pip deps + npm install
make run       # serves http://localhost:8000 with live reload

Then open http://localhost:8000, pick a drawing preset, tweak the sliders, and hit “Save SVG” or “Plot layer”.

Tests & linting

make test      # runs the Vitest suite (client + helpers)

Useful Make targets

Command What it does
make install Sets up the Python venv and installs npm deps
make run Launches the Python dev server with live reload
make dev Installs deps, runs the manifest watcher, and starts the dev server
make test Executes Vitest (JS unit tests)
make clean Removes temp files, venv, and node_modules
make manifest Rebuilds drawings/manifest.json after adding drawings

Contributor Notes

  • Please coordinate with the repo owner before committing or pushing changes; never publish work without explicit confirmation.

Plotting Pipeline

  1. Pick a drawing - Bouwkamp codes, phyllotaxis spirals, spirograph families, Voronoi sketches, or Hilbert curves ship as presets; each exposes paper, margin, and color controls.
  2. Preview in mm - the UI shows paper outlines, rulers, and margin sliders so the SVG framing matches your tape on the physical board.
  3. Generate layered SVG - the client writes an Inkscape-ready SVG where each color sits in its own layer with stroke widths pulled from the selected medium preset (e.g., Sakura 0.45 mm round tip, Molotow ONE4ALL 2 mm).
  4. Stream to hardware - pressing “Plot layer” posts the SVG + layer id to /plotter; the Python server writes a temp file, shells out to bin/axicli, and forwards stdout/stderr lines as SSE events.
  5. Monitor progress - the log panel shows real-time output, estimated times, and completion/error markers; SSE keeps the UI hot even if the process is long-running.
  6. Archive outputs - every successful save lands in output/<drawing>/<timestamp>.svg with configuration comments so you can reproduce the run later.

Preview Worker Architecture

The preview tab does all heavy lifting inside a dedicated web worker so the UI stays responsive even when a drawing builds tens of thousands of paths. The moving pieces:

  • Main thread controller (client/js/modules/preview.js)

    • Spawns client/js/workers/renderGenerator.js when the app boots.
    • Waits for an explicit workerReady handshake before posting any render payload (to avoid racing the worker bootstrap).
    • Wraps every render request in a watchdog that logs a request id, acks, and per-stage heartbeats. If the worker stalls, the UI falls back to the main-thread renderer.
    • Cancels active renders via AbortController, mirrors progress to the in-app log, and rewrites the preview SVG layers once the worker returns passes + travelSummary.
  • Render worker (client/js/workers/renderGenerator.js)

    • Imports the shared adapters from drawings/shared/clientAdapters.js and drawings/shared/dataAdapters.js so drawing modules can reuse the same builder utilities and palette math without DOM access.
    • Responds to render messages by fetching the drawing manifest, hydrating the requested drawing config, applying control/palette overrides, and calling client/js/app.js -> generateSVG.
    • Emits renderProgress stages (dispatch_received, start, manifest_fetch_start, drawings_loaded, generate_*, travel_done) that show up in the UI log.
    • Returns lightweight layer data (passes, svgInfo, travelSummary) that the main thread hydrates back into DOM nodes.
    • Handles errors centrally (messageerror, unhandledrejection, error) and posts an explanatory renderResult so the controller can fall back gracefully.
  • Worker safety switch (drawings/shared/isWorkerSafe.js)

    • Some drawings rely on DOM APIs or browser-only features. isWorkerSafeDrawing guards those by keeping an allowlist in drawings/shared/isWorkerSafe.js; if a drawing isn’t safe, the preview renderer skips the worker entirely and stays on the main thread.

When debugging: open DevTools and watch the [preview] worker … and [renderGenerator] … logs. Every render request logs its id, ack status, and progress stages so you can pinpoint timeouts or missing imports instantly.

Drawing Algorithms & Customization

Drop new experiments in drawings/core/ (first-party) or drawings/community/ (user-contributed) and they auto-register with the UI on the next reload. Each module exports a definition built with defineDrawing, so there’s very little wiring:

import { defineDrawing, SizedDrawingConfig, createDrawingRuntime, colorPalettes } from '../shared/kit.js';

class MoireConfig extends SizedDrawingConfig {
  constructor(params = {}) {
    super({ width: 420, height: 297, ...params });
    this.seed = params.seed ?? 42;
  }
}

function drawMoireGrid(drawingConfig, renderContext) {
  const { svg, builder } = createDrawingRuntime({ drawingConfig, renderContext });
  // build geometry, append paths, etc.
  return svg;
}

export const moireDrawing = defineDrawing({
  id: 'moireGrid',
  name: 'Moire Grid',
  configClass: MoireConfig,
  drawFunction: drawMoireGrid,
  controls: [
    {
      id: 'curveDensity',
      label: 'Curve Density',
      target: 'drawingData.seed',
      inputType: 'range',
      min: 2,
      max: 12,
      step: 1,
      default: 6
    }
  ],
  presets: [
    {
      key: 'moireDefault',
      name: 'Moire Default',
      params: {
        type: 'moireGrid',
        seed: 42,
        line: { strokeWidth: 0.45 },
        colorPalette: colorPalettes.sakuraPalette
      }
    }
  ]
});

After dropping a new file in drawings/core/ or drawings/community/, run make manifest (or npm run build:drawings) so the manifest/loader refreshes without restarting the server. When you use make dev, this rebuild happens automatically in the background.

  • Hot reload - server/server_runner.py watches files so your new drawing appears after a save.
  • Constraint-aware helpers - shared adapters expose color, geometry, and SVG utilities so modules don’t need deep client imports.
  • Paper + medium presets - drop in a new pen brand or sheet size via JSON and it immediately appears in the UI selectors. Papers can specify finish, weight, absorbency, and preview colour (see Paper Presets) and you can override the colour live with the picker next to the Paper dropdown.

Adding a Drawing with Codex (AI Pair Programming)

If you’re in the Codex CLI (this exact assistant), spinning up a new module is literally a prompt away:

  1. make dev in one terminal so the watcher/manifest/server are running.
  2. In another terminal, start Codex (codex run or the provided CLI).
  3. Paste a prompt like:
Add a new drawing called "polygonTapestry" under drawings/core/. Use createDrawingRuntime to tile 6×4 randomly jittered rectangles, then hatch each polygon serpentine-style via the shared controls (spacing slider 0.2–4 mm). Include controls for polygon count, jitter, and hatch spacing, register a preset that uses the Sakura palette, rebuild the manifest, mention the new drawing in CHANGELOG/README, and run npm test.

Codex already knows the repository layout and AGENTS.md rules, so it will scaffold the module, hook up controls, rebuild drawings/manifest.json, update docs/tests, and even run npm test automatically. You can iterate conversationally (“tighten the jitter range”, “add Vitest coverage”, “document the control copy”) and watch the CLI apply patches in real time. When you’re happy, run git status to review, then commit with the suggested Conventional Commit message.

That’s it—new drawings are effectively an interactive prompt away.

Custom Drawing Controls

Expose any parameter you care about by declaring a controls array in your drawing definition. Each entry provides an id, label, target path (for example, drawingData.level), and UI metadata (inputType, min, max, step, options, etc.). The client automatically renders those sliders/selects under Drawing Settings, remembers the tweaks per drawing, and reapplies them whenever you change paper, margin, medium, or orientation.

import { attachControls } from '../shared/controlsUtils.js';

const controls = [
  {
    id: 'wavyAmplitude',
    label: 'Wavy Amplitude',
    target: 'drawingData.wavyAmplitude',
    inputType: 'range',
    min: 0,
    max: 5,
    step: 0.1,
    default: 1,
    description: 'Offsets Hilbert segments for an organic feel'
  },
  {
    id: 'segmentSize',
    label: 'Segment Size',
    target: 'drawingData.segmentSize',
    inputType: 'number',
    min: 2,
    max: 10,
    step: 1,
    default: 3
  }
]

export const hilbertDrawing = attachControls(defineDrawing({
  id: 'hilbert',
  name: 'Hilbert Curve',
  configClass: HilbertConfig,
  drawFunction: drawHilbertCurve,
  presets: [ /* ... */ ]
}), controls);

Control metadata cheatsheet

Field Purpose
id Unique key per drawing (used for persistence).
target Dot-path to mutate inside the DrawingConfig (e.g., line.spacing).
inputType "range", "number", or "select".
min / max Absolute bounds applied to the actual value.
inputMin / inputMax Optional slider bounds when using a non-linear mapping (e.g., log-scale).
step / inputStep Slider increment for the actual value or just the UI control (again useful for log sliders).
scale Set to "log10" to map the slider to a logarithmic space (great for values that span several orders).
description Appears under the control to guide users.

Overrides live alongside the drawing config, so a slider change sticks even if you swap stock, toggle rulers, or revisit the drawing later in the session.

Hatching vs. line-only drawings

Global hatch controls only make sense when a drawing emits closed polygons. Every defineDrawing call now accepts a features object (default { supportsHatching: true }) that flows through the registry, preview worker, and UI:

  • Set features: { supportsHatching: false } on line-only drawings (implicit walkers, attractors, contour-only studies). The hatch panel disappears, the preview skips hatch overrides, and the draw function can stay focused on raw line paths.
  • Leave the default for polygon-heavy drawings (Bouwkamp, Photo Triangles, Voronoi) so the hatch sliders keep working. The preview automatically applies the current global settings right before rendering.
  • Want to stash an experimental drawing without deleting it? Move the file into drawings/disabled/<core|community> so it won’t be included in drawings/manifest.json until you intentionally move it back and run make manifest.

Preview Paper Colour

Need to simulate black stock or toned paper before the plotter moves? Each paper preset may include an optional "color": "#fefefe" property in config/papers.json, and the UI now exposes a colour picker plus reset button next to the paper selector. The current colour only affects the preview/export metadata, so feel free to match whatever sheet is taped down without touching the drawing config itself.

Paper Presets

Paper definitions live in config/papers.json. Each entry can include metadata that feeds both the UI (preview colour, bleed filters, hover logs) and the plotting defaults (pressure hints, recommended spacing).

"strathmoreBristolVellum": {
  "width": 279,
  "height": 432,
  "margin": 23,
  "name": "Strathmore Bristol Vellum 2-ply",
  "description": "100% cotton vellum surface",
  "weightGsm": 280,
  "finish": "vellum",
  "absorbency": "low",
  "lineSharpness": "very-high",
  "dryingSpeed": "fast",
  "featherRisk": "very-low",
  "surfaceStrength": "excellent",
  "color": "#fefefe",
  "notes": "Outstanding for technical plotting."
}

The current presets ship with the following traits:

Paper Size (mm) Weight Finish Traits & Notes
Daler Rowney Smooth Heavyweight 297 × 420 220 gsm Smooth cartridge Low bleed, fast drying, ideal for crisp technical lines
Daler Rowney Aquafine HP 297 × 420 300 gsm Hot-pressed watercolour Slightly softer edges under heavy ink, excellent strength
Van Gogh Black Watercolour 297 × 420 360 gsm Fine grain Deep black surface; medium bleed; slower drying
Hahnemühle Harmony Hot-Pressed 305 × 406 300 gsm Hot-pressed Warm white tone, balanced absorbency, great strength
Strathmore Bristol Vellum 2-ply 279 × 432 ~280 gsm Vellum (100% cotton) Very sharp lines, minimal feathering, perfect for technical plotting
Hahnemühle Skizze 190 297 × 420 190 gsm Smooth sketch Lightweight sheet for tests and drafts
Cass Art Bristol A3 297 × 420 190 gsm Smooth bristol Crisp white surface, low bleed, ideal for lighter-weight plotting

When you select a sheet, the UI renders the description + quick performance notes right under the dropdown, so you always know what is taped to the bed (even after a long debug session). These descriptions roll up the traits from JSON plus any medium-specific notes (e.g., how Sakura Microns vs. acrylic markers behave on a given stock).

The preview realism comes from client/js/utils/paperProfile.js, which merges:

  1. Baseline medium behaviour (stroke pressure, hatch spacing, jitter, bleed) from config/mediums.json.
  2. Paper metadata (finish, absorbency, surface strength) from config/papers.json.
  3. Optional overrides for specific medium + paper pairs (e.g., “Sakura on Strathmore Bristol”).

The resulting profile drives the live SVG filter (client/js/utils/previewEffects.js) that adds a subtle Gaussian blur + displacement map. The filters are injected only for the preview and stripped automatically before you export or plot, so the vector output remains crisp.

Add your own stock by copying one of the entries, tweaking the dimensions, and filling out whatever metadata makes sense for your workflow. The UI will automatically log the new traits when you select it, and the preview colour will match whatever color you set.

Color & Multi-Pen Layering

  • Smart palette selection prevents adjacent fills from sharing a color, reducing smears on real paper.
  • Layers map directly to pen labels (e.g., Black-0.5mm, Copper-2mm), so you can queue them in AxiDraw’s layer mode.
  • Medium presets store nib geometry, stroke widths, and suggested pressure settings, matching actual pen behavior.
  • Margin slider locks framing numerically + visually, so orientation swaps keep the artwork centered on both portrait and landscape sheets.

Server & Plotter Controls

  • server/server.py extends SimpleHTTPRequestHandler, serving the UI and exposing JSON commands at /plotter.
  • Supported commands include plot, stop_plot, raise_pen, toggle, align, cycle, home, and disable_motors (see docs/server_commands.md for payloads).
  • /plot-progress streams Server-Sent Events with heartbeats plus PLOT_COMPLETE / PLOT_ERROR markers so the UI can recover automatically.
  • config/plotters.json defines model numbers, servo behavior, and specs for each supported device; the server loads it via plotter_config.py, so switching models is as simple as changing the "default" entry.
  • Resume flow – every plot now passes --output_file output/plot_resume.log. If you stop a job (UI Stop button or Ctrl‑C) the log sticks around, /resume-status reports that a resume is available, and the Plotter panel enables a Resume Plot button. Clicking it shells axicli output/plot_resume.log --mode res_plot --progress (still wrapped with caffeinate/systemd-inhibit) so you can continue without re-rendering the drawing. Launching a new plot overwrites the log so the button always targets the most recent attempt.
  • Auto-home safeguard – clicking Plot Layer automatically raises the pen and walks home before spawning axicli --mode layers, so a previously paused plot can’t restart from a mid-sheet position and stress the hardware. The manual Home button runs the same sequence and clears any resume file.
  • Max travel slider – the Plotter Control tab’s Medium panel adds a “Max Travel Per Layer” slider (1–100 m plus an ∞ stop). Values come from the current paper/medium combo but can be overridden; the runtime splits any path/layer that would exceed the cap before plotting so you can reload paint or ink at predictable intervals.

Development & Testing

  • Install dependenciesmake install (Python venv + npm).
  • Run the dev servermake dev (rebuilds the drawings manifest, watches for changes, and launches the Python server with autoreload).
  • Testsnpm test runs the Vitest suite. The shared tests cover the drawing registry, control helpers, render contexts, preview heuristics (drawings/shared/__tests__/previewProfile.test.js), and the SVG filter plumbing (client/js/utils/__tests__/previewEffects.test.js) without touching the network.
  • Formattingnpm run format runs Prettier across client/, drawings/, server/, and scripts/. Run it before opening a PR so diffs stay clean.
  • Static analysisnpm run typecheck executes tsc --noEmit with allowJs + checkJs, focusing on the shared drawing runtime, render-context math, hatch controls, and the SSE progress plumbing. DOM-heavy helpers such as client/js/utils/svgUtils.js and client/js/utils/drawingBuilder.js are temporarily annotated with // @ts-nocheck until we have typed shims—leave the comment in place (or add a typed declaration) if you edit those files.
  • One-shot verificationmake verify chains lint, typecheck, and the Vitest/Python suites. Run make hooks once to install a Git pre-commit hook that runs make precommit (lint + typecheck + tests) automatically before every commit.
  • Drawing manifestnpm run build:drawings (or make manifest) must be executed after adding or renaming drawing modules so the client can discover them.
  • Code style – the repo sticks to modern ES modules, ESLint defaults, and Vitest assertions. Python files follow PEP 8 and reuse the logging helpers in server/server.py.
  • Contributing – see CONTRIBUTING.md for workflow details, commit conventions, and review expectations.

Roadmap & Known Issues

Near-term roadmap

  • Drawing scaffolder – provide a CLI (npm run scaffold:drawing) that generates boilerplate modules/tests to continue lowering the barrier for new community submissions.
  • Deeper integration coverage – now that the core endpoints are covered, expand the tests to exercise resume logs, stop/start flows, and multi-layer plotting.
  • Plotter presets – ship tuned Bantam Tools NextDraw + custom pen presets so users can jump between hardware profiles without editing JSON by hand.

Known issues

None right now—ThreadingHTTPServer keeps /plot-progress responsive, the /drawings/* handler now canonicalizes requests, and /plotter//plot-progress//save-svg all have integration tests. If you spot a regression, open an issue and we’ll jump on it.

Community, Support & Contributions

License & Credits

  • MIT License - see LICENSE
  • Uses Evil Mad Scientist’s AxiDraw CLI (bin/axicli) under their terms.
  • Huge thanks to C.J. Bouwkamp for the perfect square research, the Clipper library for polygon ops, and the broader algorithmic art community for years of shared techniques.
  • Developed by Jupiter Jayna with heavy inspiration from acrylic plotter workflows; sample work: https://plotter.art

Built with AI Pair Programming

Development leaned on AI copilots (aider.chat, DeepSeek R1, Claude 3 Sonnet) for fast iteration while keeping humans in the loop for aesthetics, safety, and hardware testing.

Appendix: Parameter Cheatsheet

Quick A3 references for popular algorithms (tweak to taste and to match your pens/papers):

Algorithm A3-friendly settings
Lissajous Curves Frequencies 3–9, phase offsets 0.1–0.3 rad, amplitude ≈ 90 % of the shorter dimension, 2k–4k samples.
Superformula m 3–12, n1 n2 n3 between 0.3–8, radius around 130 mm with 1–3 mm layer offsets. Avoid exponents > 12 unless you want brutal spikes.
Clifford Attractor Start at a 1.7–1.9, b 1.8–2.1, c 0.1–0.5, d 0.6–1.2. Plot 150k–400k iterations; add ~0.1 mm jitter for smoky strokes.
Hilbert Curve Depth 6–7 fills A3 at sane density. Scale to ~260 × 260 mm; rotate 45° if you want a less rigid footprint.
Turing Patterns Feed 0.02–0.06, kill 0.05–0.07, diffusion ratio roughly 1 : 0.5 for classic stripes, 500×500 grid, downsample to ~150–250 contours.
Phyllotaxis Divergence 137.5°, radial step 3–6 mm, 600–1200 points, jitter up to 0.5 mm for liveliness.
Epicycloids / Hypotrochoids R 80–140 mm, r 20–70 mm, d 10–50 mm, 4k–12k points, layer rotations 2–8°.
Voronoi / Lloyd sketches Seed count 150–450 with 1–2 relaxations. Clip to ≈ 380 × 260 mm. Use thick pens sparingly unless you want bold geography.
Flow Fields Noise scale 0.01–0.04, step length 1–2 mm, 2k–6k particles, 200–800 integration steps.
Lorenz Attractor σ = 10, β ≈ 2.6–3.0, ρ ≈ 25–30, dt 0.005–0.015, 80k–400k iterations (< 0.2 mm stroke).
Ikeda Attractor u 0.85–0.98, 80k–250k iterations, smoothing 0–0.05 to tame crossings.
Peter de Jong Attractor Parameters ad between −3–3, 120k–400k iterations, smoothing 0–0.1 for smoky layers.
Contour Maps (noise) Noise freq 0.003–0.02, octaves 2–5, contour spacing 3–6 units, map ≈ 400 × 280 mm.
Wave Interference 3–7 emitters, wavelengths 60–200 mm, random phases, draw contours at 0.0/0.2/0.4/0.6.
Circle Packing Radii 3–18 mm (bimodal works nicely), rejection distance 1.05–1.2× radius sum, 200–600 circles for comfortable density.
Truchet Tiles Grid 20×30 up to 40×60, two or three motifs, rotation skewed toward 0°/180° for long arcs.
Sorting-Algorithm Arcs Array size 80–200, gentle shuffle for smoother arcs, bubble sort for density, quicksort for broad structure.