three-slicer 0.1.6 → 0.2.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +310 -22
  3. package/THIRD-PARTY-NOTICES.md +37 -0
  4. package/components/README.md +2 -0
  5. package/components/dist/SettingsPanel.js +92 -92
  6. package/data/config-schema.json +698 -0
  7. package/data/filaments.js +1 -1
  8. package/data/preset-keys.json +1 -0
  9. package/data/printers.json +1 -1
  10. package/data/processes.js +1 -1
  11. package/data/ui-tree.json +342 -0
  12. package/engine/README.md +279 -3
  13. package/engine/examples/sla_headless.mjs +51 -0
  14. package/engine/index.d.ts +77 -5
  15. package/engine/index.js +15 -4
  16. package/engine/src/client.js +165 -0
  17. package/engine/src/data.js +3 -0
  18. package/engine/src/preset_file.js +99 -0
  19. package/engine/src/settings.js +232 -0
  20. package/engine/src/sla_core.js +188 -0
  21. package/engine/src/sla_request.js +185 -0
  22. package/engine/src/slicer.worker.js +76 -4
  23. package/engine/src/slicer_core.js +0 -0
  24. package/engine/src/slicer_core.mt.js +0 -0
  25. package/package.json +26 -11
  26. package/types/client.d.ts +88 -0
  27. package/types/components.d.ts +10 -0
  28. package/types/data/preset-keys.json.d.ts +20 -0
  29. package/types/data/printers.json.d.ts +2 -2
  30. package/types/data.d.ts +4 -0
  31. package/types/settings-keys.d.ts +107 -1
  32. package/types/settings.d.ts +116 -1
  33. package/types/sla.d.ts +167 -0
  34. package/types/viewer.d.ts +59 -1
  35. package/types/worker.d.ts +95 -0
  36. package/viewer/README.md +163 -3
  37. package/viewer/dist/Viewport.js +3633 -3137
  38. package/viewer/dist/model_loaders.js +1 -1
  39. package/viewer/dist/parse_3mf-C2X_W442.js +300 -0
  40. package/viewer/dist/parse_3mf.worker.js +1 -1
  41. package/viewer/styles.css +5 -0
  42. package/types/gen_settings_types.mjs +0 -70
  43. package/viewer/dist/parse_3mf-8oCnCJNK.js +0 -216
package/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 — 2026-08-19
4
+
5
+ SLA (resin) printing lands as a second technology in the same kernel, routed by `printer_technology`.
6
+
7
+ ### Added
8
+
9
+ - SLA slicing in the WASM kernel: `slicer.sliceSla(stl, params)` on the engine handle,
10
+ `client.sliceSla` / `client.sliceSlaJob` on the worker client. Results carry per-layer mask segment
11
+ streams, generated support and pad meshes, `resin_ml` / `time_estimate` stats and `lift_layers`
12
+ (pad + elevation) — no G-code.
13
+ - The support chain is PrusaSlicer 2.9.6's own, ported verbatim: support-point generator
14
+ (SupportPointGenerator + SupportIslands), the default support tree, and the real pad geometry. Built
15
+ against vendored NLopt 2.5.0, Prusa's bundled libigl, SGI glu-libtess, and CGAL headers.
16
+ - `deriveSlaParams(settings)`, `printerTechnology(settings)`, `resinCatalog` / `resinSettingsFor(name)`
17
+ in `three-slicer/settings`; SLA machine profiles and the resin material catalog in the data files.
18
+ - Typed capability errors instead of silent approximation: `SLA_UNSUPPORTED_HOLLOWING` (hollowing/drain
19
+ holes), `SLA_UNSUPPORTED_ORGANIC` (organic trees), `SLA_PAD_AROUND_OBJECT_UNSUPPORTED` (zero-elevation
20
+ pad embedding). `SLA_CAPABILITIES` from `three-slicer/client` is the machine-readable map.
21
+ - Viewer: an SLA printer profile swaps the filament card for the resin card, previews the support/pad
22
+ meshes with the lifted layer frame, and exports `.sl1` archives — portrait PNG masks (the SL1 family's
23
+ panel mounting) plus upstream's `config.ini` field set.
24
+ - `<SettingsPanel/>` renders the SLA tabs for an SLA profile — no prop, it follows the settings map.
25
+ - `.3mf` SLA records (manual support points, drain holes) survive import/export round-trip.
26
+ - A runnable SLA example: `node node_modules/three-slicer/engine/examples/sla_headless.mjs`.
27
+ - `THIRD-PARTY-NOTICES.md` documenting the licenses compiled into the shipped kernels.
28
+
29
+ ### Notes
30
+
31
+ - The multithreaded kernel carries the SLA path too and is pinned byte-identical to the single-threaded
32
+ one over the same SLA slice.
33
+ - FFF output is unchanged: the golden byte-identical G-code check holds across this release.
34
+
35
+ Earlier releases (0.1.x) predate this changelog.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # three-slicer
2
2
 
3
- Browser/WASM 3D-printing slicer package derived from [OrcaSlicer](https://github.com/OrcaSlicer/OrcaSlicer). It can slice binary STL files to G-code in Node or the browser, and it also ships React UI pieces for building a browser slicer: a three.js viewport, GPU toolpath preview, and a schema-driven settings panel.
3
+ Browser/WASM 3D-printing slicer package derived from [OrcaSlicer](https://github.com/OrcaSlicer/OrcaSlicer). It can slice binary STL files to G-code in Node or the browser, and it also ships React UI pieces for building a browser slicer: a three.js viewport, GPU toolpath preview, and a schema-driven settings panel. SLA (resin) printers are a second technology in the same kernel — supports and pad from PrusaSlicer's ported chain, previewed in the same viewer, exported as an `.sl1` archive.
4
4
 
5
5
  The package is published as a single npm package, `three-slicer`, with subpath exports for the engine, viewer, components, worker, and extracted OrcaSlicer data.
6
6
 
@@ -23,6 +23,7 @@ The package is published as a single npm package, `three-slicer`, with subpath e
23
23
  - Support painting and material painting, plus per-tool filament and purge statistics.
24
24
  - 3MF **project** import in the viewer: a slicer-written `.3mf` (OrcaSlicer/BambuStudio save, MakerWorld download) restores its plate layout, project settings, and painted facets — not just the meshes.
25
25
  - Extracted data files for custom UIs: config schema, UI tree, toggle rules, invalidation map, and the printer/process/filament catalogs.
26
+ - SLA (resin) slicing: PrusaSlicer 2.9.6's ported support-point generator, support tree and pad, a resin material catalog, and `.sl1` mask export — routed by `printer_technology`, with typed capability errors for what the port does not cover.
26
27
  - Automatic multithreaded WASM selection on cross-origin-isolated browser pages.
27
28
 
28
29
  ## Installation
@@ -31,14 +32,18 @@ The package is published as a single npm package, `three-slicer`, with subpath e
31
32
  npm i three-slicer
32
33
  ```
33
34
 
34
- `react`, `react-dom`, and `three` are peer dependencies and npm installs them for you. They stay peers rather than dependencies because React and three must be single instances — a duplicate copy breaks hooks and `instanceof`.
35
+ `react`, `react-dom`, and `three` are **optional** peer dependencies. They stay peers rather than dependencies because React and three must be single instances — a duplicate copy breaks hooks and `instanceof` — and optional because only the UI half needs them: a headless consumer installs none of the three.
35
36
 
36
- Only the viewer and components import them. If you use the headless engine, they are installed into `node_modules` but never enter your bundle:
37
+ Install them yourself when you use the viewer or the settings panel:
37
38
 
38
- | Import path | Needs react/three at runtime |
39
+ ```bash
40
+ npm i three-slicer react react-dom three
41
+ ```
42
+
43
+ | Import path | Needs react/three |
39
44
  | --- | --- |
40
- | `three-slicer`, `/settings`, `/toggle`, `/worker`, `/wasm`, `/data` | no |
41
- | `three-slicer/viewer`, `/viewer/toolpath`, `/viewer/loaders` | yes |
45
+ | `three-slicer`, `/settings`, `/toggle`, `/client`, `/worker`, `/wasm`, `/data` | no |
46
+ | `three-slicer/viewer`, `/viewer/toolpath`, `/viewer/loaders`, `/viewer/gcode` | yes |
42
47
  | `three-slicer/components` | react only |
43
48
 
44
49
  ## Quick Start: Headless Slicing
@@ -66,7 +71,11 @@ console.log(result.gcode)
66
71
  slicer.dispose()
67
72
  ```
68
73
 
69
- `slice()` accepts a binary STL as an `ArrayBuffer` or `Uint8Array`. Parameters can be either a kernel params object or a JSON string.
74
+ `slice()` accepts a binary STL as an `ArrayBuffer` or `Uint8Array`. Parameters can be either a kernel params object or a JSON string — every accepted parameter is listed in the [kernel parameter reference](engine/README.md#kernel-parameter-reference), which is generated from the kernel's own reader.
75
+
76
+ A runnable version of the above ships with the package: `node node_modules/three-slicer/engine/examples/headless.mjs` writes a cube, slices it batch and streamed, and prints the stats.
77
+
78
+ `result.error` is set instead of a result when a slice fails or is cancelled — check it before reading `gcode`.
70
79
 
71
80
  ## Streaming Layers
72
81
 
@@ -102,15 +111,22 @@ export default function App() {
102
111
  const [settings, setSettings] = useState({})
103
112
 
104
113
  return (
105
- <Viewport
106
- settings={settings}
107
- setSettings={setSettings}
108
- />
114
+ <div style={{ position: 'relative', width: '100%', height: '100vh' }}>
115
+ <Viewport
116
+ settings={settings}
117
+ setSettings={setSettings}
118
+ />
119
+ </div>
109
120
  )
110
121
  }
111
122
  ```
112
123
 
113
- The viewer handles model loading, drag and drop, transform controls, multi-plate layout, worker slicing, GPU toolpath preview, and G-code export. It supports STL, OBJ, 3MF (including the production extension used by Orca/Bambu/Prusa), AMF, and PLY out of the box; other formats such as STEP can be added with `registerLoader()`. A `.3mf` written by a slicer is treated as a project, not just geometry: its plate layout, project settings, and support/material painting are restored on import (where a facet carries both paint kinds, material paint wins and the dropped support paint is reported). Viewer and component styles are bundled into their Shadow DOM roots, so host app CSS does not need to import package CSS.
124
+ **The container is not optional.** The viewer fills its nearest *positioned* ancestor — the shadow host is
125
+ `display: contents` and the shell inside is `position: absolute; inset: 0` — so it needs a parent with
126
+ `position: relative` and a real height. In a plain static `<div>` it escapes to the page instead. There is no width
127
+ or height prop.
128
+
129
+ The viewer handles model loading, drag and drop, transform controls, multi-plate layout, worker slicing, GPU toolpath preview, and G-code export. Its keyboard and mouse bindings, the undo boundary, and what a host cannot drive are documented in [viewer/README.md](viewer/README.md). It supports STL, OBJ, 3MF (including the production extension used by Orca/Bambu/Prusa), AMF, and PLY out of the box; other formats such as STEP can be added with `registerLoader()`. A `.3mf` written by a slicer is treated as a project, not just geometry: its plate layout, project settings, and support/material painting are restored on import (where a facet carries both paint kinds, material paint wins and the dropped support paint is reported). Viewer and component styles are bundled into their Shadow DOM roots, so host app CSS does not need to import package CSS.
114
130
 
115
131
  ## Quick Start: Settings Panel
116
132
 
@@ -181,12 +197,14 @@ Available subpaths:
181
197
  | `three-slicer` | Headless WASM engine SDK |
182
198
  | `three-slicer/settings` | Schema defaults and settings-to-kernel mapping |
183
199
  | `three-slicer/toggle` | Enable/disable rule evaluation for settings UIs |
184
- | `three-slicer/worker` | Browser worker entry |
200
+ | `three-slicer/worker` | Browser worker entry, and the typed message protocol |
201
+ | `three-slicer/client` | `createSlicerClient()` — promises over that protocol |
185
202
  | `three-slicer/wasm` | Single-threaded Emscripten WASM glue |
186
203
  | `three-slicer/wasm-mt` | Multithreaded Emscripten WASM glue |
187
204
  | `three-slicer/viewer` | React `<Viewport/>` |
188
205
  | `three-slicer/viewer/toolpath` | GPU toolpath renderer utilities |
189
206
  | `three-slicer/viewer/loaders` | Model loaders |
207
+ | `three-slicer/viewer/gcode` | `parseGcode(text)` — G-code back into the layer stream the renderer consumes |
190
208
  | `three-slicer/components` | React `<SettingsPanel/>` |
191
209
  | `three-slicer/data` | Named exports for extracted metadata |
192
210
  | `three-slicer/data/*.json` | Extracted OrcaSlicer metadata |
@@ -236,6 +254,92 @@ const result = slicer.slice(stlArrayBuffer, params)
236
254
 
237
255
  `deriveKernelParams()` maps the curated set of schema keys currently supported by the kernel (92 today). Other schema keys can still be displayed by the UI, but they may not affect slicing output yet. Vector options are simplified to their first element, except the filament options described below, which keep every extruder's entry.
238
256
 
257
+ ## 3MF Projects
258
+
259
+ A slicer-written `.3mf` is a project, not a mesh file: `Metadata/project_settings.config` holds the flattened
260
+ preset its author sliced with. `<Viewport/>` reads all of it on import. The project parser/writer currently remain
261
+ viewer internals; there is no `three-slicer/viewer/project` public export yet. Hosts can normalize or serialize a
262
+ raw project settings object with the public settings helpers below, but should not import viewer source files.
263
+
264
+ **Every value in that file is a string**, and reading it raw is not a cosmetic problem — a disabled option is the
265
+ string `"0"`, and `"0"` is truthy, so a raw import turns every disabled option ON. A point is the string `"256x256"`
266
+ while every consumer indexes it as `[x, y]`, so a raw `printable_area[1][0]` is the character `'2'`. Both are
267
+ handled by coercing each value by its config-schema type:
268
+
269
+ ```js
270
+ import { normalizeProjectSettings, serializeProjectSettings } from 'three-slicer/settings'
271
+
272
+ const { settings, applied, skipped } = normalizeProjectSettings(rawConfig)
273
+ // settings: real JS types, ready for deriveKernelParams() and <SettingsPanel/>
274
+ // applied : how many keys survived
275
+ // skipped : keys the config schema does not define (preset bookkeeping, version fields) — dropped, not coerced
276
+ ```
277
+
278
+ Writing one back is the exact inverse, and it matters for the same reason: a raw JS `false` would be read by any
279
+ other slicer's parser as the string `"false"`, which is truthy.
280
+
281
+ ```js
282
+ const config = serializeProjectSettings(settings) // bool -> "1"/"0", point -> "XxY", points group -> "X1xY1,X2xY2"
283
+ ```
284
+
285
+ Upstream's `inherits` / `different_settings_to_system` reconciliation is deliberately not reproduced: it exists to
286
+ rebase a stored preset onto a local vendor preset database, and `project_settings.config` is already flattened, so
287
+ its values are taken as written.
288
+
289
+ ## Preset Files
290
+
291
+ OrcaSlicer's "Config files" dialog imports a `.json` holding one preset — a printer (`machine`), print settings
292
+ (`process`) or a material (`filament`) — plus the zip forms `.orca_printer` / `.orca_bundle` / `.orca_filament`.
293
+ Both directions are supported.
294
+
295
+ ```js
296
+ import { writePresetFile, readPresetFile, presetOptionKeys, printerSettings } from 'three-slicer/settings'
297
+
298
+ // Write. Flattened on purpose: every option of that type, no `inherits`, so the file stands on its own.
299
+ const file = writePresetFile(settings, { type: 'machine', name: 'My printer' })
300
+ // -> { type: 'machine', name: 'My printer', from: 'User', printable_area: ['0x0','256x0',…], … }
301
+
302
+ // Read. `resolveParent` follows `inherits`; without it a vendor file arrives incomplete (see below).
303
+ const { settings: loaded, missingParent, skipped } = readPresetFile(file,
304
+ { resolveParent: (name) => printerSettings(name) })
305
+ ```
306
+
307
+ `presetOptionKeys(type)` is the key set for one type — 175 for `machine`, 370 for `process`, 149 for `filament`,
308
+ taken from upstream's own `Preset::printer_options()` and friends. It is what splits a settings map, which holds
309
+ every type's keys at once, into the file for one of them.
310
+
311
+ **A vendor preset is a diff, not a preset.** Upstream saves a derived preset as the difference against its parent
312
+ (`Preset::save`: *"only save difference if it has parent"*), so a file off the vendor profiles carries a fraction
313
+ of what it appears to. Read Bambu's X1C machine file without resolving `inherits` and it has no `printable_area`
314
+ and no `printable_height` at all — the bed is in the parent. `missingParent` names the parent when it could not be
315
+ resolved; the file's own values are still applied, because dropping the file entirely loses more than it protects.
316
+
317
+ Values are written and read in upstream's own encoding — every value a string, a point as `"XxY"`, per-extruder
318
+ options as arrays of strings — the same shape [3MF Projects](#3mf-projects) uses, since upstream writes both
319
+ through the same serializer.
320
+
321
+ The viewer adds the zip forms and a UI: the printer card's **Load** and **Save** read and write these files, and a
322
+ save routes through [`onExport`](viewer/README.md#taking-the-saves) like every other download.
323
+
324
+ ## Settings UI: Enable/Disable Rules
325
+
326
+ OrcaSlicer greys out options that another option makes irrelevant. Those rules are extracted, and
327
+ `three-slicer/toggle` evaluates them against a settings map — the panel does this internally, and a custom UI can
328
+ do the same:
329
+
330
+ ```js
331
+ import { makeCfg, disabledKeys } from 'three-slicer/toggle'
332
+
333
+ const disabled = disabledKeys(makeCfg(settings))
334
+ // setting key -> the enable_if expression that evaluated false, e.g.
335
+ // { outer_wall_jerk: 'false, variant_index', … }
336
+ if (disabled.outer_wall_jerk) { /* grey the control out; the value says why */ }
337
+ ```
338
+
339
+ Only rules that evaluate to an unambiguous `false` are reported — an expression referring to something the
340
+ evaluator cannot resolve is left out rather than guessed at, so a key missing from the map means "not known to be
341
+ disabled", not "definitely enabled".
342
+
239
343
  ## Materials and Multi-Material
240
344
 
241
345
  A material is a filament preset — temperatures, flow, diameter, cooling, and the retraction/z-hop overrides a material may apply on top of the machine's. Read the catalog through the facade rather than decoding the data file:
@@ -249,7 +353,22 @@ filaments.recommendedFor('Bambu Lab X1 Carbon 0.4 nozzle') // the vendor's sho
249
353
  const material = filaments.settingsFor('Bambu PLA Basic @BBL X1C')
250
354
  ```
251
355
 
252
- The filament key set is disjoint from the process key set, so applying a material never clears a process pick and vice versa.
356
+ **Clear before you apply.** A preset carries only the keys it sets, so merging one over another leaves the previous
357
+ pick's leftovers behind — an ABS material followed by a PLA one keeps ABS's chamber temperature. Every facade
358
+ exposes the exact key set to remove first:
359
+
360
+ ```js
361
+ function applyPreset(settings, preset, keys) {
362
+ const next = { ...settings }
363
+ for (const key of keys) delete next[key]
364
+ return Object.assign(next, preset)
365
+ }
366
+
367
+ setSettings(s => applyPreset(s, filaments.settingsFor(name), filaments.keys))
368
+ ```
369
+
370
+ The same applies to `processPresets().keys` and `printerKeys`. The filament key set is disjoint from the process key
371
+ set, so applying a material never clears a process pick and vice versa.
253
372
 
254
373
  **One material per extruder.** OrcaSlicer stores every filament option as one entry per extruder, so a multi-material settings map writes each extruder's material at its own index:
255
374
 
@@ -281,15 +400,98 @@ worker.postMessage({ cmd: 'erase', facet, hx, hy, hz, cx, cy, cz, radius })
281
400
 
282
401
  Because a single enum serves both jobs, a support blocker paint and an Extruder 2 paint are the same mark on a facet, and painted materials only take effect with support turned off (the painted multi-material path emits no support). Support painting is unaffected: a support-enabled slice stays on the single-material path.
283
402
 
403
+ ## SLA (Resin) Slicing
404
+
405
+ SLA is a second printer technology, not an FFF mode. `printer_technology` in the settings map routes it —
406
+ apply an SLA machine profile from the catalog (`printersByVendor` marks the technology) or set the key
407
+ yourself, then derive SLA params and call the SLA entry:
408
+
409
+ ```js
410
+ import { printerTechnology, deriveSlaParams, resinCatalog, resinSettingsFor } from 'three-slicer/settings'
411
+
412
+ printerTechnology(settings) // 'SLA' or 'FFF'
413
+ const params = deriveSlaParams(settings) // layers/exposure, display, supports, pad, raster orientation
414
+
415
+ const result = slicer.sliceSla(stl, params)
416
+ result.stats.sla // true — no G-code; stats carry resin_ml and time_estimate
417
+ result.stats.layers // includes the pad + elevation lift below the model
418
+ result.layers[0].paths // the same stride-8 segment stream the FFF preview reads
419
+ ```
420
+
421
+ In a worker, `client.sliceSla(stl, params, { onProgress, onLayer })` is the same call over the message
422
+ protocol, and `client.sliceSlaJob(job)` takes the typed job protocol — objects with manual support points,
423
+ drain-hole records and modifier volumes — validated structurally before slicing.
424
+
425
+ A runnable version ships with the package: `node node_modules/three-slicer/engine/examples/sla_headless.mjs`
426
+ slices a cube with supports and pad, then demonstrates the capability refusal below.
427
+
428
+ The support chain is PrusaSlicer 2.9.6's own, ported verbatim and compiled into the same WASM kernel: the
429
+ support-point generator, the default support tree, and the real pad geometry. Resin materials follow the
430
+ same catalog pattern as filaments: `resinCatalog` lists them, `resinSettingsFor(name)` is one preset.
431
+
432
+ **What the kernel cannot do it refuses with a typed code — it never approximates.** A request the port does
433
+ not cover fails with a stable error instead of silently producing something else:
434
+
435
+ | Code | Refused request |
436
+ | --- | --- |
437
+ | `SLA_UNSUPPORTED_HOLLOWING` | `hollowing_enable` or drain holes — a solid slice answered to a hollow request would be a mislabeled print |
438
+ | `SLA_UNSUPPORTED_ORGANIC` | organic support trees |
439
+ | `SLA_PAD_AROUND_OBJECT_UNSUPPORTED` | zero-elevation pad embedding (`pad_around_object`) |
440
+
441
+ `SLA_CAPABILITIES` (exported from `three-slicer/client`) is the machine-readable capability map. A `.3mf`
442
+ carrying SLA records survives round-trip regardless: manual support points and drain holes are preserved on
443
+ import and written back on export, even where slicing refuses to consume them.
444
+
445
+ The viewer follows the technology automatically: an SLA profile swaps the filament card for the resin card,
446
+ hides the FFF-only tools (prime tower, painting brushes), previews the generated support and pad meshes, and
447
+ exports an `.sl1` archive — per-layer PNG masks in the SL1 family's portrait orientation plus upstream's
448
+ `config.ini` — instead of G-code.
449
+
284
450
  ## Worker Usage
285
451
 
286
- Use a module worker to run slicing off the browser main thread.
452
+ Slicing blocks its thread for seconds, so in a browser it belongs in a worker. `three-slicer/client` wraps the
453
+ worker's message protocol in promises:
454
+
455
+ ```js
456
+ import { createSlicerClient } from 'three-slicer/client'
457
+ import { deriveKernelParams } from 'three-slicer/settings'
458
+
459
+ const client = createSlicerClient() // creates and owns the worker
460
+ await client.warmup() // optional: load the kernel before the first slice needs it
461
+
462
+ const { gcode, stats } = await client.slice(stlArrayBuffer, deriveKernelParams(settings), {
463
+ onProgress: (done, total) => setProgress(done / total),
464
+ })
465
+
466
+ client.cancel() // stops a running slice — see below
467
+ client.terminate()
468
+ ```
469
+
470
+ Pass `onLayer` to take the layers as they are produced instead of having them assembled, which is what keeps a
471
+ large model out of memory:
472
+
473
+ ```js
474
+ await client.slice(stl, params, { onLayer: ({ z, idx, gcode }) => append(gcode) })
475
+ // the result then carries stats only
476
+ ```
477
+
478
+ The same client drives painting — `prepare`, `paint`, `erase`, `clear`, `overlay`, `importPaint`, `exportPaint`.
479
+ It deliberately leaves two things to the host: the viewer's progress weighting (measured against its own models)
480
+ and its out-of-memory retry ladder, both of which are UI policy rather than protocol.
481
+
482
+ ### Driving the worker directly
483
+
484
+ `createSlicerClient` is a thin wrapper; the protocol underneath is a plain message contract, fully typed in
485
+ `three-slicer/worker`.
287
486
 
288
487
  ```js
289
488
  import { engineWorkerURL } from 'three-slicer'
489
+ import { deriveKernelParams } from 'three-slicer/settings'
290
490
 
291
491
  const worker = new Worker(engineWorkerURL(), { type: 'module' })
292
492
 
493
+ const chunks = []
494
+
293
495
  worker.onmessage = (event) => {
294
496
  const message = event.data
295
497
 
@@ -298,20 +500,50 @@ worker.onmessage = (event) => {
298
500
  }
299
501
 
300
502
  if (message.type === 'layer') {
301
- console.log(message.idx, message.z, message.gcode)
503
+ chunks.push(message.gcode) // paths/widths arrive transferred, not copied
302
504
  }
303
505
 
304
506
  if (message.type === 'done') {
305
- console.log(message.stats)
507
+ console.log(chunks.join(''), message.result.stats)
306
508
  }
307
509
 
308
510
  if (message.type === 'error') {
309
511
  console.error(message.error)
310
512
  }
311
513
  }
514
+
515
+ // A slice request carries NO `cmd` — it is the worker's default action. Every other message (paint, prepare,
516
+ // overlay, …) is addressed by `cmd`, so leaving it off is what selects slicing.
517
+ // `params` must be a JSON STRING here. The direct handle's slice() also accepts an object and stringifies it for
518
+ // you; the worker hands its argument straight to the kernel, which parses JSON text.
519
+ worker.postMessage({ stl: stlArrayBuffer, params: JSON.stringify(deriveKernelParams(settings)) }, [stlArrayBuffer])
520
+ ```
521
+
522
+ The worker replies with `progress`, `layer`, `done`, and `error` while slicing. `done` carries `{result}` — its
523
+ `stats` when the layers were streamed, plus `gcode` and `layers` when they were not. For bundler-specific setups,
524
+ `three-slicer/worker` exposes the worker entry directly.
525
+
526
+ ### Cancelling a slice
527
+
528
+ Cancellation is a flag the kernel polls from inside its C++ loop, so it works even while the worker is blocked in
529
+ WASM. It lives in a `SharedArrayBuffer`, which means it is only available on the multithreaded kernel — that is, on
530
+ a [cross-origin isolated](#multithreaded-wasm) page. `client.cancel()` handles this and returns `false` when there
531
+ is no flag to write. Driving the worker yourself, the address arrives once, unprompted:
532
+
533
+ ```js
534
+ let cancelView = null
535
+
536
+ worker.addEventListener('message', ({ data }) => {
537
+ if (data.type === 'supsab' && data.cancelPtr) {
538
+ cancelView = new Uint32Array(data.buf, data.cancelPtr, 1)
539
+ }
540
+ })
541
+
542
+ const cancel = () => cancelView && Atomics.store(cancelView, 0, 1)
312
543
  ```
313
544
 
314
- The worker protocol emits `progress`, `layer`, `done`, and `error` messages. For bundler-specific setups, `three-slicer/worker` exposes the worker entry directly.
545
+ Without cross-origin isolation the engine runs single-threaded, no `supsab` message arrives, and a slice in
546
+ progress cannot be interrupted — terminate the worker instead.
315
547
 
316
548
  ## Bundler Notes
317
549
 
@@ -379,23 +611,76 @@ the layout by hand.
379
611
 
380
612
  ## API Reference
381
613
 
614
+ ### `three-slicer`
615
+
382
616
  | API | Description |
383
617
  | --- | --- |
384
618
  | `createSlicer()` | Loads the WASM kernel and returns a slicer handle |
385
619
  | `slicer.slice(stl, params, callbacks?)` | Slices binary STL input to G-code or streamed layers |
386
- | `slicer.paintPrepare(stl)` | Prepares painting against a model |
620
+ | `slicer.sliceSla(stl, params, onProgress?)` | SLA slice — layer masks, support/pad meshes, resin stats; typed `error` on a refused capability |
621
+ | `slicer.paintPrepare(stl)` | Prepares painting against a model; returns the facet count |
387
622
  | `slicer.paint(args)` | Paints enforcer/blocker data (boolean pair; the state-addressed form is worker-only) |
388
623
  | `slicer.paintClear()` | Clears painting data for every state |
389
- | `slicer.overlay(enforcer)` | Returns painting overlay data |
390
- | `slicer.heapSize()` | Returns current WASM heap size |
624
+ | `slicer.overlay(enforcer)` | The painted overlay triangles for one state, as a `Float32Array` |
625
+ | `slicer.heapSize()` | Current WASM heap size, bytes (peak, monotonic) |
391
626
  | `slicer.dispose()` | Releases the slicer handle for garbage collection |
392
627
  | `engineWorkerURL()` | Returns a browser worker URL |
393
- | `deriveKernelParams(settings)` | Converts sparse OrcaSlicer settings to kernel params |
628
+
629
+ ### `three-slicer/settings`
630
+
631
+ | API | Description |
632
+ | --- | --- |
633
+ | `deriveKernelParams(settings, opts?)` | Converts sparse OrcaSlicer settings to kernel params. `opts.plate` picks a plate's entry from per-plate options |
634
+ | `schemaDefault(key)` | The config-schema default for a key |
635
+ | `settingRaw(settings, key)` | The map's value, or the schema default |
636
+ | `settingScalar(settings, key)` | The same, reduced to a scalar — the first set entry of a per-extruder column |
637
+ | `normalizeProjectSettings(raw)` | A 3mf's all-strings `project_settings.config` → a typed settings map |
638
+ | `serializeProjectSettings(settings)` | The inverse, for writing one |
639
+ | `writePresetFile(settings, opts)` | A settings map → an OrcaSlicer preset `.json`, flattened |
640
+ | `presetFileText(settings, opts)` | The same, stringified |
641
+ | `readPresetFile(raw, opts)` | A preset `.json` → a settings map; follows `inherits` via `resolveParent` |
642
+ | `presetOptionKeys(type)` | The option keys belonging to `machine` / `process` / `filament` |
643
+ | `printersByVendor` | Vendor → profile name → entry, for building a picker |
394
644
  | `printerSettings(name)` | Settings a vendor machine profile applies |
645
+ | `printerKeys` | Every key a printer profile can set — clear these before applying another |
646
+ | `printerDefaultPreset(name)` | The vendor's recommended process preset for that printer |
647
+ | `machineLimitKeys` | The schema keys the kernel's machine limits are read from |
395
648
  | `processPresets()` | Lazy facade over the print (process) preset catalog |
396
649
  | `filamentPresets()` | Lazy facade over the material preset catalog |
650
+ | `printerTechnology(settings)` | `'SLA'` or `'FFF'` — what routes a slice to `slice_sla` |
651
+ | `deriveSlaParams(settings)` | Sparse settings → the SLA kernel's params (display, supports, pad, raster) |
652
+ | `resinCatalog` / `resinSettingsFor(name)` | The resin material catalog, and one preset's settings |
653
+
654
+ ### `three-slicer/toggle`
655
+
656
+ | API | Description |
657
+ | --- | --- |
658
+ | `makeCfg(settings)` | Wraps a settings map for rule evaluation |
659
+ | `disabledKeys(cfg)` | Setting key → the `enable_if` expression that evaluated false |
660
+ | `evalEnableIf(expr, locals, cfg)` | Evaluates a single rule; `null` when it cannot be resolved |
661
+
662
+ ### `three-slicer/viewer` and `three-slicer/components`
663
+
664
+ | API | Description |
665
+ | --- | --- |
397
666
  | `<Viewport/>` | React slicer viewport |
398
667
  | `<SettingsPanel/>` | React settings form |
668
+ | `loadModel(name, buffer)` | Parses STL/OBJ/3MF/AMF/PLY into objects |
669
+ | `registerLoader(exts, fn)` | Adds a format — STEP and anything else needing a heavy dependency |
670
+ | `SUPPORTED_EXT` / `fileExt(name)` | The known extensions, and one file's |
671
+ | `splitConnectedComponents(pos)` | Splits a mesh into its connected components; `null` when there is only one |
672
+ | `parseGcode(text)` | G-code text → the layer stream the renderer consumes |
673
+ | `buildSegmentData(layers, w)` | Layers → the packed segment buffers |
674
+ | `computeColors(data, view, ctx)` | Per-segment colours for a view type |
675
+ | `makeToolpath(THREE, data)` | The GPU toolpath mesh handle |
676
+ | `VIEW_TYPES` / `roleRatios(lengths)` | The preview's view types, and the per-role length split |
677
+
678
+ ### `three-slicer/data`
679
+
680
+ | API | Description |
681
+ | --- | --- |
682
+ | `schema`, `uiTree`, `toggleRules`, `invalidationMap`, `printers` | The extracted metadata, import attributes already applied |
683
+ | `loadProcesses()` / `loadFilaments()` | The two large catalogs, on demand — prefer the `settings` facades |
399
684
 
400
685
  ## Runtime Support
401
686
 
@@ -416,6 +701,9 @@ the layout by hand.
416
701
  - Some vector settings are simplified to their first element.
417
702
  - Material painting and support cannot currently produce two materials on the same slice: the painted multi-material path emits no support, and one triangle selector serves both brushes.
418
703
  - The multi-material prime tower is a real ported wipe tower, but the fallback square ring used when it fails is not.
704
+ - SLA hollowing and drain-hole geometry are refused with `SLA_UNSUPPORTED_HOLLOWING` (the OpenVDB chain is not ported); the records still round-trip through `.3mf`.
705
+ - The `.sl1` export lives in the viewer (its masks need a canvas); there is no headless `.sl1` writer export yet.
706
+ - SLA mask edges are canvas anti-aliasing, not upstream's AGG rasterizer — same geometry, slightly different edge pixels.
419
707
  - Multithreaded WASM requires cross-origin isolation.
420
708
  - `three` is pinned as a peer dependency because viewer internals depend on the `TransformControls` API shape.
421
709
 
@@ -0,0 +1,37 @@
1
+ # Third-party notices
2
+
3
+ `three-slicer` is licensed AGPL-3.0-or-later (see `LICENSE.txt`). The distributed artifacts — the WASM
4
+ kernels embedded in `engine/src/slicer_core.js` / `slicer_core.mt.js` and the bundled viewer/components —
5
+ contain compiled or bundled code from the following projects. Full license texts ship in the source
6
+ repository at the paths given (the package's `repository` field points there); the kernel's vendored copies
7
+ live under `packages/wasm-core/third_party/deps_src/`.
8
+
9
+ ## Compiled into the WASM kernels
10
+
11
+ | Component | License | Source of the vendored copy |
12
+ | --- | --- | --- |
13
+ | OrcaSlicer (the FFF slicing code this package is ported from) | AGPL-3.0-or-later | github.com/SoftFever/OrcaSlicer |
14
+ | PrusaSlicer 2.9.6 (the SLA support/pad chain, ported verbatim under `packages/wasm-core/slasupport_port/`) | AGPL-3.0-or-later | github.com/prusa3d/PrusaSlicer |
15
+ | Clipper (Angus Johnson) | BSL-1.0 | `deps_src/clipper/` |
16
+ | Clipper2 | BSL-1.0 | `deps_src/clipper2/` |
17
+ | Boost (header-only use) | BSL-1.0 | headers at build time |
18
+ | Eigen (header-only) | MPL-2.0 | headers at build time |
19
+ | CGAL 6 (header-only use, GMP/MPFR disabled) | GPL-3.0-or-later | headers at build time |
20
+ | libigl (headers; both the Orca-generation copy and Prusa's bundled copy) | MPL-2.0 | `deps_src/libigl/`, `deps_src/libigl_prusa/` |
21
+ | libnest2d (headers) | LGPL-3.0 | `deps_src/libnest2d/` (its `LICENSE.txt`) |
22
+ | NLopt 2.5.0 | LGPL-2.1-or-later for the combined library (`deps_src/nlopt/COPYING`); per-algorithm terms in each `algs/*/COPYRIGHT` — the most restrictive is `algs/luksan` (LGPL-2.1+), most others (e.g. `algs/newuoa`) are MIT-style Powell/MIT notices | `deps_src/nlopt/` |
23
+ | SGI glu-libtess (the GLU tessellator) | SGI Free Software License B 2.0 | `deps_src/glu-libtess/` (header of each source file) |
24
+ | Emscripten runtime (the generated JS glue around the kernels) | MIT | emscripten.org |
25
+
26
+ ## Bundled into the viewer/components builds
27
+
28
+ | Component | License | Note |
29
+ | --- | --- | --- |
30
+ | fflate (via three.js examples) | MIT | zip/unzip in the 3mf and SL1 codecs |
31
+
32
+ `three`, `react` and `react-dom` are peer dependencies and are not bundled or redistributed by this package.
33
+
34
+ The LGPL components (NLopt's luksan directory, libnest2d headers) are compiled into a work distributed under
35
+ AGPL-3.0-or-later, which satisfies their license terms; their complete license texts accompany the source
36
+ repository as noted above. Nothing in this file overrides a component's own license — where this summary and
37
+ a vendored license file disagree, the vendored file is authoritative.
@@ -6,6 +6,8 @@ Reusable React components for the browser slicer. Props-driven — no global sta
6
6
 
7
7
  An OrcaSlicer-style settings form generated from `three-slicer/data` (schema + UI tree + toggle rules): tab/page/group navigation, mode filter (simple/advanced/expert), search across 923 options, dirty markers with per-option reset, and enable/disable rules evaluation.
8
8
 
9
+ The tab set follows the printer profile's technology: a settings map whose `printer_technology` says SLA swaps the FFF tabs for the SLA ones (print/material/printer builders carry the technology in their upstream builder name, which is what the filter reads). No prop needed — change the profile in the shared settings map and the panel follows.
10
+
9
11
  ```bash
10
12
  npm i three-slicer # react is a peer dependency — npm installs it for you
11
13
  ```