lineadraw 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # lineadraw
2
2
 
3
- Type definitions and marketplace tooling for [Linea](https://lineadraw.com)
4
- blocks and commands — the one devDependency a block/command repository
5
- needs. Zero runtime: your modules import types from `"lineadraw"`, and the
6
- Linea editor provides the real implementation when it runs them.
3
+ [Linea](https://lineadraw.com) for Node: the headless drawing SDK, the
4
+ typings for blocks and commands, and the marketplace CLI — the one
5
+ dependency a script, an agent, or a block/command repository needs.
7
6
 
8
7
  - **Typings** — `lineadraw.d.ts` makes `import { defineBlock } from
9
8
  "lineadraw"` and `import { … } from "lineadraw/helpers"` type-check in
10
9
  any editor/CI, with parameter types inferred into your `draw` function.
10
+ - **SDK** — `import { openDrawing } from "lineadraw/sdk"`: open, edit,
11
+ render and export `.linea` documents headlessly
12
+ ([below](#headless-sdk-lineadrawsdk)).
11
13
  - **CLI** — `lineadraw manifest | check | init` turns any repository into
12
14
  a Linea **marketplace**: a git repo the editor can browse and install
13
15
  blocks/commands from.
@@ -65,10 +67,40 @@ pull them along); `paramVisibility({ params })` hides property rows;
65
67
  `previewInputs` curates the inputs used for thumbnails. Units are
66
68
  millimeters, Y-up, angles in radians CCW.
67
69
 
70
+ Parameter types (the same rows serve a command's `params`):
71
+
72
+ - `number` — `min`/`max`, `integer`, `step`, `prefix`/`suffix` ("1:",
73
+ "mm"), `presets` (standard values in the field's dropdown).
74
+ - `string` — `placeholder`, `multiline`, `presets`.
75
+ - `boolean` (`display: "toggle"` — a toggle button showing the label,
76
+ compact in a row), `enum` (`options`, each optionally with a `swatch:
77
+ "#rrggbb"` color square; `display: "buttons"` — a segmented toggle group
78
+ for short choices, falling back to a dropdown when it doesn't fit),
79
+ `color` (`"#rrggbb"`, `presets` = a palette).
80
+ - `numberList` (`number[]`, typed Tekla-style: `"0 2*7200"` →
81
+ `[0, 7200, 7200]`) and `stringList` (`string[]`: `'A "B 2"'` →
82
+ `["A", "B 2"]`), both with `presets` and `placeholder` (shown while
83
+ empty).
84
+ - Pickers over the document's resources, valued by id / name:
85
+ `textStyle`, `dimStyle`, `font`, `lineType`, `hatchPattern`.
86
+ - `label` — a section heading (no value); `row` — 2–3 compact params on one
87
+ panel row (`items`, each label a short prefix like "W"); its items are
88
+ ordinary flat params (`params.w`).
89
+
90
+ Every param may carry a `hint` (the label's tooltip). An instance that
91
+ overrides a param shows "Reset" beside it in Properties, back to your
92
+ default. `draw` also receives
93
+ `styles`: `styles.text(id)` / `styles.dim(id)` give a document style (an
94
+ empty or unknown id → the first), for blocks that lay out with real text
95
+ sizes.
96
+
68
97
  ## Writing a command
69
98
 
70
99
  A command is a macro with a single `defineCommand` default export. Its
71
- whole run is one undo step; Esc during a pick reverts cleanly.
100
+ whole run is one undo step; Esc during a pick reverts cleanly. A
101
+ `params` table (the same rows a block's `params` uses) is asked for in a
102
+ dialog before the run and arrives typed as `ctx.params`; headless callers
103
+ pass it by name.
72
104
 
73
105
  ```ts
74
106
  // commands/circle-here.cmd.ts
@@ -95,13 +127,24 @@ helpers) — the editor resolves both when executing your module.
95
127
  lineadraw manifest Validate all modules and write .lineadraw/marketplace.json
96
128
  lineadraw check Validate without writing; fail if the committed manifest is stale
97
129
  lineadraw init Add the marketplace structure to the current npm project
130
+
131
+ lineadraw status Is the Linea desktop app serving agents? port, open editors
132
+ lineadraw editors The open drawings: id, name, path, kind, dirty
133
+ lineadraw open <file.linea> Open a file in a new window
134
+ lineadraw close [--editor <x>] [--discard]
135
+ lineadraw call <operation> ['<json>'] [--editor <x>] Run one linea_* tool
136
+ lineadraw tools The operations the bridge offers
98
137
  ```
99
138
 
139
+ The bridge commands talk to the running Linea desktop app on
140
+ `127.0.0.1:7431` (the app's `bridge.json` supplies the port and the agent
141
+ token; `--port`, `--token` and `LINEA_BRIDGE_TOKEN` override) — see
142
+ docs/desktop-bridge.md in the linea repo.
143
+
100
144
  The manifest is deterministic (repo-relative paths, no git metadata), so
101
145
  `check` is a byte-for-byte diff — the included CI workflow runs it on every
102
- push and PR. With `@lineadraw/sdk` installed as a devDependency,
103
- `manifest`/`check` additionally **dry-run every block** and render
104
- thumbnails; without it the CLI still fully works.
146
+ push and PR. `manifest`/`check` also **dry-run every block** through
147
+ `lineadraw/sdk` and render thumbnails.
105
148
 
106
149
  Collection name and owner come from your package.json:
107
150
 
@@ -111,14 +154,179 @@ Collection name and owner come from your package.json:
111
154
 
112
155
  ## Using a marketplace in the editor
113
156
 
114
- In [the editor](https://lineadraw.com): **Library → Marketplace tab → +**
115
- and enter `owner/repo` (or `owner/repo@branch`). For a private repository,
157
+ In [the editor](https://lineadraw.com): **Library → Marketplace tab → the
158
+ gear button (Manage marketplaces…) → Add a marketplace** and enter
159
+ `owner/repo` (or `owner/repo@branch`). For a private repository,
116
160
  create a fine-grained personal access token with read-only Contents access
117
161
  to just that repository and paste it in the add dialog — it is stored only
118
162
  on that device. Installing embeds the module (and its declared dependency
119
163
  closure) in the document, so drawings keep working offline and travel with
120
164
  the file.
121
165
 
166
+ ## Headless SDK (`lineadraw/sdk`)
167
+
168
+ A script-friendly API over the Linea CAD kernel: open, create, query, edit,
169
+ render, and import/export `.linea` documents headlessly in Node — no browser,
170
+ no server. Built for agents, CLIs, and batch tooling.
171
+
172
+ ```bash
173
+ npm i lineadraw
174
+ ```
175
+
176
+ ```js
177
+ import { newDrawing, openDrawing } from "lineadraw/sdk";
178
+
179
+ const doc = newDrawing();
180
+ const walls = doc.addLayer({ name: "walls", color: "#e8e8e8", lineWidth: 0.5 });
181
+ doc.add([
182
+ { type: "polyline", layerId: walls, closed: true,
183
+ points: [[0, 0], [6000, 0], [6000, 3000], [0, 3000]] },
184
+ { type: "dimension", points: [[0, 0], [6000, 0]], offset: [0, -500],
185
+ scale: 50 }, // annotations × drawing scale
186
+ ]);
187
+ doc.createLayout("Plan 1:50", { sheet: "A3", viewport: { scale: "1:50" } });
188
+ await doc.exportPng("check.png", { width: 1600 }); // verify by LOOKING at it
189
+ await doc.save("plan.linea");
190
+ await doc.exportPdf("plan.pdf");
191
+ ```
192
+
193
+ ### Conventions (read once, avoid every common bug)
194
+
195
+ - **Units are millimeters, Y-up. Angles are radians CCW** — everywhere in the
196
+ object model and in `rotate()`. Draw 1:1 (a 6 m wall is 6000 mm); paper
197
+ scale lives in layout viewports (`"1:50"` strings via `parseScale`).
198
+ - **Never write `.linea` JSON by hand.** Every mutation goes through
199
+ kernel-validated ops (`add`/`update`/`remove`/transforms); invalid input
200
+ throws with the failing object and field named.
201
+ - **Style by layer**: unset `color`/`lineWidth`/`lineType` on objects inherit
202
+ from the layer. Layer names are unique (`addLayer` throws on duplicates)
203
+ and can be used instead of ids (`layer: "walls"`, query filters). Note:
204
+ `locked` layers are an editor-UI concept — the SDK does not block edits.
205
+ - **Layer `print: false`** (plot flag) keeps a layer visible on screen and in
206
+ SVG/PNG but excludes it from PDF output — construction/guide layers.
207
+ - **Opacity is byLayer too**: `opacity` (0..1) on a layer is the default for
208
+ its objects; an object's own `opacity` overrides it. Paints across canvas,
209
+ SVG, and PDF; DXF/DWG export drops it (no direct mapping).
210
+ - **Annotation scale is per-object**: for a 1:N drawing set the top-level
211
+ `scale: N` on text, dimensions, and hatches, keeping `textHeight` at
212
+ paper size (2.5 body / 3.5 / 5 titles). `scale` is NOT a `styleOverride`
213
+ key; `add`/`update` reject it there.
214
+ - **Reads are detached snapshots**: `doc.document`, `doc.objects`,
215
+ `getObject`, `query()` return deep clones — mutating them does nothing.
216
+ - **New documents are layout-less**; `exportPdf` of a layout-less document
217
+ falls back to one auto-fitted A3 page.
218
+ - `transaction(fn)` groups edits into one undo step. `array(ids, count,
219
+ delta)`: `count` INCLUDES the originals; the returned ids are the copies.
220
+ - An arc with `startAngle === endAngle` renders as a full circle.
221
+ - There is no rectangle type — use a closed 4-point polyline.
222
+
223
+ ### API surface
224
+
225
+ `newDrawing(name?)` / `drawingFromJson(text)` / `openDrawing(path)` → `Drawing`:
226
+
227
+ - **Name**: `drawing.name` is the file name (set on `openDrawing`, updated
228
+ by `save(path)`); `newDrawing(name)` names it until it is saved. The
229
+ `.linea` JSON itself carries no name.
230
+
231
+ - **Read**: `summary()`, `objects`, `layers`, `layouts`, `getObject`,
232
+ `query({ids, type, layer, color, lineType, content, bbox, inside, near, layout})`,
233
+ `measure(id)` (`bboxOf(ids)`, `length(id)`, `area(id)`), `listLayers()`,
234
+ `listLayouts()`, `listBlocks()`, `listCommands()`, `revision`
235
+ - **Edit**: `add(objects, {layout?})`, `update(ids, patch)` or
236
+ `update([{id, ...patch}])`, `remove(ids)` (→ count), `reorder(ids,
237
+ "front" | "back" | {above} | {below})`, `transaction(fn)` (sync or async;
238
+ a throw rolls back), `undo()`, `redo()`, `setDimStyle`,
239
+ `setTextStyle`, `applyOps(ops)`, `ready()`
240
+ - **Modules**: `await installBlocks(sources)` / `removeBlock(id)`,
241
+ `await installCommands(sources)` / `removeCommand(id)`,
242
+ `await setBlockDefinitions(sources)`, `await setCommands(sources)`
243
+ - **Scripts**: `await runScript(source, inputs?, params?)` runs a
244
+ `defineCommand` module (the same one the editor palette runs) headlessly
245
+ as ONE undo step — its declared `inputs` (points) are supplied up front,
246
+ its declared `params` by name (`{ count: 3 }`; omitted ones take their
247
+ defaults, unknown names and wrong types throw); `runCommand(id, inputs?,
248
+ params?)` runs one of the document's own commands. Returns the script's
249
+ return value.
250
+ - **Layers**: `addLayer`, `updateLayer(nameOrId, patch)`,
251
+ `removeLayer(nameOrId)` (must be empty and not current),
252
+ `reorderLayers(order)`, `setCurrentLayer(nameOrId)`
253
+ - **Assets**: `addAsset(dataUrl) → assetId` (for `image` objects;
254
+ content-keyed), `removeAsset(id)` (refused while referenced)
255
+ - **Transform**: `move`, `rotate` (radians), `scale`, `mirror`, `copy`, `array`
256
+ - **Layouts**: `createLayout(name, {sheet, scale, viewport, index})`,
257
+ `updateLayout(nameOrId, {name, sheet, printPalette})`, `removeLayout`,
258
+ `reorderLayouts`, `parseScale("1:50")`. `sheet` is `"A3"` (landscape when
259
+ creating, the current orientation when patching), `{ name: "A3",
260
+ orientation: "portrait" }`, or `{ width, height }` in mm.
261
+ - **Export**: `renderSvg`/`exportSvg`, `renderPng`/`exportPng`, `await toDxf()`/`exportDxf`,
262
+ `toDwg`/`exportDwg` (DWG R2000–2018, default AC1027), `renderPdf`/`exportPdf`,
263
+ `toJson`/`save`
264
+ - **Import (merge)**: `importDxf(path)`/`importDxfText(text)` (with
265
+ `unitOverride`), `importDwg(path)`/`importDwgData(bytes)` (DWG R13–R2018),
266
+ `importPdfData(arrayBuffer)`
267
+
268
+ Full parameter docs are in the bundled `dist/index.d.ts` JSDoc.
269
+
270
+ ### Object types
271
+
272
+ `line` (a, b) · `polyline` (points with optional bulge, closed) · `circle` ·
273
+ `arc` (CCW start→end) · `ellipse` (center, majorAxis vector, ratio) ·
274
+ `hatch` (loops — outer + holes, even-odd; fill solid/pattern/lines) ·
275
+ `text` (position, content; **default alignment center/center**; optional
276
+ leaderLines with the arrowhead at index 0) · `dimension` (linear/angular/
277
+ radial/diameter; `offset` is a vector from points[0] to the dimension line;
278
+ linear with >2 points = chain) · `block` (instances of script definitions) ·
279
+ `viewport` (layouts only) · `image` (placement of an asset).
280
+
281
+ ### Block scripts
282
+
283
+ `document.blockDefinitions` is an array of ES-module **source strings**
284
+ (`await doc.setBlockDefinitions([...sources])` — async so the script
285
+ transpiler is loaded and block geometry is definitive when it resolves).
286
+ A block is one `defineBlock` call:
287
+
288
+ ```ts
289
+ import { defineBlock } from "lineadraw";
290
+ import { polar, add } from "lineadraw/helpers"; // vector-math helpers
291
+
292
+ export default defineBlock({
293
+ id: "chair", // instances reference this
294
+ name: "Chair",
295
+ params: [{ name: "width", type: "number", default: 450 }],
296
+ draw: ({ params }) => [
297
+ // Plain object DTOs in block-local coordinates; params.width: number.
298
+ { type: "line", a: { x: 0, y: 0 }, b: { x: params.width, y: 0 } },
299
+ ],
300
+ });
301
+ ```
302
+
303
+ Optional members: `place` — an array of point labels (one pick per label)
304
+ or an interactive `({ params, pickPoint, pickObject })` function run once
305
+ at insertion (default is one insertion point) — and
306
+ `paramVisibility({ params })` (hides property rows). `params` may be the
307
+ table literal or a zero-arg function returning it. Imports are limited to
308
+ `"lineadraw"` and `"lineadraw/helpers"`. The instance's first point input
309
+ is its pivot: `draw` sees inputs localized to it, and instance
310
+ `rotation`/`scale` apply about it. (Plain named exports — incl. the
311
+ pre-rename `main`/`defineInput` names — remain evaluatable as the frozen
312
+ wire form.)
313
+
314
+ ### Interchange notes
315
+
316
+ - DXF/DWG export flattens semantics: block instances are exported as their
317
+ evaluated geometry (no `INSERT` reuse) and `image` objects are skipped
318
+ (DXF images reference external files). The export result reports both in
319
+ `skipped`.
320
+ - PDF import extracts vector strokes and text runs (dimensions arrive as
321
+ exploded geometry). pdf.js loads lazily on first use, as do the PNG
322
+ rasterizer (native resvg) and the DWG transcoder (bundled WebAssembly).
323
+ - `renderSvg({ bbox })` culls objects outside the box (they are absent from the
324
+ file, not merely outside the viewBox).
325
+ - Opening a document written by a NEWER Linea version throws instead of
326
+ loading lossily.
327
+ - Dimensions validate their point counts per kind (linear ≥ 2, angular ≥ 3,
328
+ radial/diameter ≥ 2).
329
+
122
330
  ## Ecosystem
123
331
 
124
332
  - [Linea editor](https://lineadraw.com) — the browser CAD app that runs
@@ -126,8 +334,5 @@ the file.
126
334
  - [`lineadraw/lineadraw`](https://github.com/lineadraw/lineadraw) — the
127
335
  public collection: real blocks/commands to learn from, agent skill, and
128
336
  guides.
129
- - [`@lineadraw/sdk`](https://www.npmjs.com/package/@lineadraw/sdk) —
130
- headless Node API over the same kernel (open/edit/render/export
131
- `.linea` documents; also powers the CLI's block dry-runs).
132
337
  - [`@lineadraw/mcp`](https://www.npmjs.com/package/@lineadraw/mcp) — MCP
133
338
  server with the full editor as an in-chat app.