How-to
On npmDrawings and files
Autosave to any store, the drawing file format, saving and opening, the clipboard, and pictures of a drawing.
A drawing leaves the engine in two ways: autosave, which writes every change to a store you choose, and files, which the user saves and opens with AutoCAD's commands. Both carry the same text, the drawing file.
Autosave
Pass a DrawingStore and the engine restores the drawing from it when it starts, then writes it
back after every change:
interface DrawingStore { read(): string | null write(text: string): void}writeis called once a burst of changes settles (about a third of a second), and once more ondispose()and when the page is hidden or left, so the last change is never lost.readis called once, when the engine starts. Text that is not a drawing gives an empty one, never an error.state.saveState("idle","saving","saved") andstate.lastSavedAtdrive a save indicator.
The engine has two stores built in:
| Store | Where it keeps the drawing |
|---|---|
createMemoryDrawingStore(text?) | In memory while the page lives, starting from text if given |
createLocalStorageDrawingStore() | In this browser's localStorage, under one fixed key |
The second one keeps a single drawing per site: every page of yours that uses it shares, and
overwrites, the same one, as the MellowCAD editor does by default. Use it for one editor per site.
readSavedDrawingSummary() reads how many objects and layers that slot holds without loading it,
for a "continue drawing" link. null from createLocalStorageDrawingStore() means there is no
storage (a server, or a browser that blocks it).
Your own store
write is synchronous and read runs before the engine exists, so a store backed by a server
fetches the drawing first and sends each write without waiting:
import { CadEngine, type DrawingStore } from "@mellowcad/engine"
const url = "/api/drawings/42"const saved = await fetch(url).then((response) => (response.ok ? response.text() : null))
const store: DrawingStore = { read: () => saved, write: (text) => { void fetch(url, { method: "PUT", body: text, keepalive: text.length < 60_000 }) },}
const engine = new CadEngine(canvas, { store })The engine already waits for changes to settle, so write is not called on every mouse move.
keepalive lets the last write finish while the page closes; browsers refuse it for a body over
64 KB, so a bigger drawing is sent without it.
The drawing file
A drawing file is JSON in world coordinates, readable by other tools:
{ "type": "easycad-drawing", "version": 38, "generator": "mellowcad", "settings": { "unit": "mm", "precision": 2 }, "layers": [{ "id": "0", "name": "0", "color": "#e2e8f0", "visible": true, "locked": false }], "currentLayerId": "0", "entities": [ { "id": "e1", "type": "line", "layerId": "0", "start": { "x": 0, "y": 0 }, "end": { "x": 10, "y": 0 } }, { "id": "e2", "type": "circle", "layerId": "0", "center": { "x": 0, "y": 0 }, "radius": 5 } ]}(A real file has more settings and tables: styles, blocks, the UCS.)
- The format is versioned. Each release that changes it bumps
version, and every release still opens the files of the ones before. A file from a newer version is refused, with a message asking to update. - The reader is tolerant. Unknown fields are ignored, and an object it cannot read is skipped instead of failing the whole file.
- SAVE names a file
drawing-<date>.ecad.json(suggestedFileName()), and OPEN accepts.jsonand.ecad.
Saving and opening yourself
serializeDrawing() returns the file's text, and loadDrawing(text) replaces the drawing with
one, framed in the view and with an empty undo history:
async function saveToServer() { await fetch("/api/drawings/42", { method: "PUT", body: engine.serializeDrawing() })}
async function openFromServer() { const text = await fetch("/api/drawings/42").then((response) => response.text()) try { engine.loadDrawing(text) } catch (error) { alert((error as Error).message) // worded for the user }}SAVE and OPEN
When the user saves (SAVE, or QSAVE with Ctrl+S) or opens (OPEN, Ctrl+O), CadEngine downloads
the file or shows a file picker, as the browser allows; a bad file is reported on the command
line.
saveDrawingToFile() and openDrawingFromFile() do the same from a button. newDrawing() is
NEW: an empty drawing, which U brings back from.
The clipboard
COPYCLIP (Ctrl+C), CUTCLIP (Ctrl+X) and PASTECLIP (Ctrl+V) use the system clipboard, as AutoCAD
does between its drawings. The text copied is a small drawing file, so objects travel between two
tabs, or two engines, with their layers and styles. When the browser refuses to read the
clipboard, PASTECLIP pastes what was last copied in the page. pasteFromClipboard() is PASTECLIP
from a button.
Pictures of a drawing
The canvas is a picture already; for one elsewhere, render a Scene with a presenter:
- SVG, anywhere.
new SvgScenePresenter().render(scene)returns an<svg>document as a string, in a browser, on a server or at build time.themePalette()makes it use CSS custom properties instead of fixed colours, andthemeCustomProperties()declares them. - A block's thumbnail.
engine.buildBlockPreview(id, width, height)frames one block, for a palette of blocks. - The objects alone.
buildDrawingPreview(document, { width, height })frames a drawing with no grid, cursor or markers, for a thumbnail or an Open Graph image. Itsdocumentis a recording's (recordDemo, see Without a browser).
To turn a drawing file into a picture on a server, open it in a CadDriver and render its scene,
as the headless guide shows.