three-slicer 0.2.1 → 0.2.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,102 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3 — 2026-08-29
4
+
5
+ A developer-experience release: everything here comes from a log of what integrating the package actually
6
+ cost while building the four `examples/` demos and the live landing embed (`packages/FRICTION.md`). The
7
+ theme is the one failure mode they shared — wrong input returning something plausible instead of an error.
8
+
9
+ ### Added
10
+
11
+ - `sliceRequest` on `<Viewport/>`: a token whose identity change requests one slice. The component's
12
+ contract stays props-only, but a host that hides the built-in chrome no longer has to remount the whole
13
+ viewer to start a slice (which blanked the scene and re-parsed the model — measured as a visible
14
+ double-flip on the landing embed).
15
+ - `result.warnings`, a `string[]` naming what a **successful** slice got away with. Today's one entry is
16
+ `'over_bed_model'`: a bed-centred model sliced outside the printable area with plausible time and
17
+ material and no error. Present on the direct handle, through `createSlicerClient()`, and on the raw
18
+ worker's `done` reply. The `stats` flags are unchanged.
19
+ - `result.throughput` — how fast the slice ran: `ms` (wall time around the call), `kernelMs` (the kernel's own
20
+ phase total, so `ms - kernelMs` is the WASM boundary cost), `layersPerSecond`, and `msPerMsegment`. The last
21
+ one is the field to compare runs with: the kernel is not deterministic in segment count, so raw milliseconds
22
+ compare two different amounts of work. Same three paths as `warnings`, and on the SLA routes as well —
23
+ `kernelMs` there sums the resin passes (contours, sample, tree, raster, emit), since the two technologies
24
+ share only the name `t_emit_ms`.
25
+ - Throughput in the viewer, in three places: the slice bar shows layers/second **while** a slice runs, the stats
26
+ card shows the finished figure ("Sliced in 4.0s · 166 layers/s", with the kernel share and ms/Msegment on the
27
+ tooltip), and hosts receive both — `sliceRate` on `onEvent` and `throughput` beside `stats` on `onSliced`.
28
+ An SLA slice reports the live figure throughout; an FFF slice reports it once the emission pass starts
29
+ streaming layers, since its earlier passes publish no per-layer progress and none is invented. It therefore
30
+ reads higher than the whole-slice average, which counts the passes that emit nothing (measured on a 38MB
31
+ model: 306–707 layers/s live, 166 over the whole 4.0s slice).
32
+ - `applyPreset(settings, preset, keys)` in `three-slicer/settings` — clear-then-merge, so a preset switch
33
+ cannot leave the previous material's keys underneath. All four demos had written this by hand.
34
+ - `kernelSettingKeys` and `ignoredKernelSettings(settings)`: the 128 schema keys the FFF derivation reads,
35
+ and which of yours it does not. A key it does not read is accepted and does nothing.
36
+ - The raw worker's slice request accepts an object `params`, not only a JSON string — the direct handle
37
+ and the client already did, and the worker's own SLA branch always had.
38
+
39
+ ### Fixed
40
+
41
+ - The prime tower stand-in is drawn only when the plate actually changes tools — objects on more than one
42
+ extruder, or material paint reaching extruder 2 or higher — instead of whenever a second filament happened to
43
+ be loaded. With everything on T1 the slice emits no tool change and generates no tower, so the box promised
44
+ one that would not be printed.
45
+ - The viewer no longer shows a slice result that the current settings would not produce. A change to `settings`
46
+ drops the cached results and returns to Prepare, instead of leaving a preview, a print estimate and an enabled
47
+ G-code export describing the settings the slice ran with. An injected (`gcode` / `sl1`) plate and an opened
48
+ `.sl1` archive are left alone — neither claims to be a slice of these settings.
49
+ - **`createSlicerClient()` with no argument now works after `vite build`.** Its default worker is written
50
+ as the literal `new Worker(new URL(…), { type: 'module' })` that bundlers recognize as a worker entry;
51
+ routed through `engineWorkerURL()` it matched Vite's plain-asset rule instead and was copied verbatim,
52
+ still importing an unhashed `./slicer_core.js` that the build never emitted — a 404 on the first message,
53
+ invisible until a production build. Measured on a Vite 5 consumer: a 21KB copy in a 40KB dist with no
54
+ kernel at all, versus a 12KB worker chunk importing the emitted kernels. `pack_check.sh` now fails when
55
+ a worker chunk imports a file the build did not produce.
56
+ - `support_type: 'tree(auto)'` selects tree supports. Tree vs grid was routed from `support_style` alone,
57
+ so an imported OrcaSlicer project asking for tree supports sliced grid, silently.
58
+ - A `files` prop changed after mount warns once instead of being ignored in silence.
59
+
60
+ ### Changed
61
+
62
+ - `engineWorkerURL()` is documented as the **no-bundler** path (native ESM, an import map, a CDN). Under
63
+ Vite or webpack use `createSlicerClient()` or `three-slicer/worker?worker`. The function is unchanged.
64
+ - Since tree vs grid now reads `support_type`, a settings map carrying `support_type: 'tree(auto)'` with
65
+ `support_style: 'default'` produces different G-code than it did in 0.2.2 — tree, as upstream does.
66
+
67
+ ## 0.2.2 — 2026-08-22
68
+
69
+ ### Added
70
+
71
+ - SL1 import in the viewer: an `.sl1` archive opens through the file picker, drag & drop, or the new `files`
72
+ prop — rendered as a raster preview, with the model shape reconstructed from the masks in the background
73
+ (two passes: a coarse mesh in under a second, then full resolution). This viewer's own exports carry scene
74
+ and role sidecars, so reopening one shows the exported surface itself, in the sliced preview's colours, and
75
+ re-exports byte-identical. Opening an `.sl1` applies the archive's job description to the settings
76
+ (`printer_technology` first), so a fresh FFF session switches to the SLA route.
77
+ - SL1 export: role/scene sidecar members, off-thread mask encoding, and a save window.
78
+ - Move scrub in Preview: a horizontal slider that walks the top layer move by move with a screen-fixed nozzle
79
+ marker — upstream's sequential view. Position changes reach the host as the `moveScrub` event.
80
+ - `files` and `sl1` Viewport props: models, `.3mf` projects, `.sl1` archives and preset files handed in once
81
+ at mount.
82
+
83
+ ### Fixed
84
+
85
+ - The SLA print area is the resin display size, not `printable_area`.
86
+
87
+ ## 0.2.1 — 2026-08-21
88
+
89
+ ### Added
90
+
91
+ - `pad_around_object` (embed) is now supported in the SLA path — the `SLA_PAD_AROUND_OBJECT_UNSUPPORTED`
92
+ capability gate is gone. It forces zero elevation (upstream `is_zero_elevation`), and an empty embed pad is
93
+ legal: the ring survives only where supports stand.
94
+
95
+ ### Performance
96
+
97
+ - SLA slicing: the raster fallback was rewritten, and slicing/prepare run layer-parallel on the multithreaded
98
+ kernel — byte-identical output, with progress bands matched to measured time.
99
+
3
100
  ## 0.2.0 — 2026-08-19
4
101
 
5
102
  SLA (resin) printing lands as a second technology in the same kernel, routed by `printer_technology`.
package/README.md CHANGED
@@ -12,6 +12,20 @@ The package is published as a single npm package, `three-slicer`, with subpath e
12
12
  - Source: [kimgh06/Web_Three_Slicer](https://github.com/kimgh06/Web_Three_Slicer)
13
13
  - Demo: [slicer.kimgh06.com](https://slicer.kimgh06.com/)
14
14
 
15
+ ## Start Here
16
+
17
+ Three ways in — pick the row that is your case. Each path is a working code block plus the one doc worth
18
+ reading whole; everything else is lookup.
19
+
20
+ | Your case | Path | Copy this | Then read |
21
+ | --- | --- | --- | --- |
22
+ | "Just the numbers" — quotes, farms, automation. No UI, no React | **Headless** | [Quick Start: Headless Slicing](#quick-start-headless-slicing) | [engine/README](engine/README.md) (134 lines), params via [PARAMS.md](engine/PARAMS.md) as lookup |
23
+ | A slicer inside your existing page | **Embed** | [Quick Start: React Viewer](#quick-start-react-viewer) | [viewer/README](viewer/README.md) (330 lines — the props contracts live there) |
24
+ | The full slicer UI — viewport + settings panel | **Full UI** | [Full React Example](#full-react-example) | viewer/README + [components/README](components/README.md) |
25
+
26
+ Whichever path: read [Before You Ship](#before-you-ship) before deploying — those traps return plausible
27
+ results instead of errors, and the first two never show up in `npm run dev`.
28
+
15
29
  ## Features
16
30
 
17
31
  - WASM slicing engine with Arachne walls, infill patterns, supports, raft/brim/skirt, ironing, arc fitting, multi-material paths, and layer streaming.
@@ -23,7 +37,9 @@ The package is published as a single npm package, `three-slicer`, with subpath e
23
37
  - Support painting and material painting, plus per-tool filament and purge statistics.
24
38
  - 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
39
  - 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.
40
+ - SLA (resin) slicing: PrusaSlicer 2.9.6's ported support-point generator, support tree and pad, a resin material catalog, and `.sl1` mask export *and import* — routed by `printer_technology`, with typed capability errors for what the port does not cover.
41
+ - Preview move scrub: a position slider with a nozzle marker that walks the toolpath inside a layer (upstream's sequential view).
42
+ - Host injection props on the viewer: `files` hands models, `.3mf` projects, `.sl1` archives and preset files in at mount; `gcode` injects a sliced result for preview-only use.
27
43
  - Automatic multithreaded WASM selection on cross-origin-isolated browser pages.
28
44
 
29
45
  ## Installation
@@ -42,8 +58,8 @@ npm i three-slicer react react-dom three
42
58
 
43
59
  | Import path | Needs react/three |
44
60
  | --- | --- |
45
- | `three-slicer`, `/settings`, `/toggle`, `/client`, `/worker`, `/wasm`, `/data` | no |
46
- | `three-slicer/viewer`, `/viewer/toolpath`, `/viewer/loaders`, `/viewer/gcode` | yes |
61
+ | `three-slicer`, `/settings`, `/toggle`, `/client`, `/worker`, `/wasm`, `/data`, `/viewer/gcode`, `/viewer/toolpath` | no — `makeToolpath(THREE, data)` takes `three` as an argument |
62
+ | `three-slicer/viewer`, `/viewer/loaders` | yes |
47
63
  | `three-slicer/components` | react only |
48
64
 
49
65
  ## Quick Start: Headless Slicing
@@ -71,12 +87,37 @@ console.log(result.gcode)
71
87
  slicer.dispose()
72
88
  ```
73
89
 
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.
90
+ `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/PARAMS.md), which is generated from the kernel's own reader.
75
91
 
76
92
  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
93
 
78
94
  `result.error` is set instead of a result when a slice fails or is cancelled — check it before reading `gcode`.
79
95
 
96
+ `result.warnings` is the other half of that: a `string[]`, empty when nothing, naming what a **successful** slice
97
+ got away with. Today the one entry is `'over_bed_model'` — the model sliced outside the printable area, which
98
+ otherwise reports plausible time and material and no error (see [Before You Ship](#before-you-ship)). Every path
99
+ carries it: the direct handle, `createSlicerClient()`, and the raw worker's `done` reply.
100
+
101
+ `result.throughput` says how fast it ran, measured around the call on the same three paths:
102
+
103
+ ```js
104
+ result.throughput.ms // wall time, incl. the STL crossing into WASM and the layer stream out
105
+ result.throughput.kernelMs // the kernel's own phase total; ms − kernelMs is the boundary cost
106
+ result.throughput.layersPerSecond // the figure to show a person
107
+ result.throughput.msPerMsegment // the figure to COMPARE runs with
108
+ ```
109
+
110
+ Compare runs on `msPerMsegment`, not on `ms`. The kernel is not deterministic in segment count — the same input
111
+ has been measured 15% apart between runs — so two raw millisecond figures describe two different amounts of work,
112
+ and the difference reads as a regression that is not there. In the viewer the same throughput is live: the slice
113
+ bar shows layers/second while slicing, and the host receives it as the `sliceRate` event.
114
+
115
+ SLA slices carry it too (`sliceSla`, the object-aware job, and the JS contour fallback). `kernelMs` sums that
116
+ technology's own passes — contours, sample, tree, raster, emit — because the two share only the name `t_emit_ms`,
117
+ and reading an SLA result through the FFF phase list would report its emit pass as the entire kernel time
118
+ (measured on a 10mm cube: 2.99ms summed correctly, 1.42ms if mis-read). Compare SLA runs against SLA runs: its
119
+ `path_segments` counts mask segments, not toolpath segments.
120
+
80
121
  ## Streaming Layers
81
122
 
82
123
  For large models, pass callbacks to receive each sliced layer as it is produced. This avoids keeping the full G-code and layer payload in memory at once.
@@ -209,9 +250,36 @@ Available subpaths:
209
250
  | `three-slicer/data` | Named exports for extracted metadata |
210
251
  | `three-slicer/data/*.json` | Extracted OrcaSlicer metadata |
211
252
 
253
+ ## Architecture: How the Pieces Connect
254
+
255
+ One npm package, five modules. The dependency direction is one-way: `wasm-core` →(build)→ `engine`
256
+ ←(data)← `data`, with `components` and `viewer` consuming different surfaces of the engine, `types`
257
+ generating and gating the contract from the side — and the glue at the top is always a single sparse
258
+ settings map held by the host app.
259
+
260
+ | Module | Role | Published as | Depends on | Consumed by |
261
+ | --- | --- | --- | --- | --- |
262
+ | `wasm-core` | C++ kernel sources + vendored third_party | not published | (ported upstream sources) | `engine` — as build output only |
263
+ | `engine` | kernel SDK: direct handle, worker, client, settings derivation | `three-slicer` root + `/settings` `/toggle` `/client` `/worker` `/wasm(-mt)` `/data` | committed wasm-core output, `data`, the generated `kernel_setting_keys.js` | `components`, `viewer`, every consumer |
264
+ | `data` | extracted schema + catalogs (config-schema, printers, processes, filaments…) | `/data` | `extract_all.py` ← upstream | `engine`'s settings, `components`, `viewer` |
265
+ | `types` | every `.d.ts` + two generators | attached per subpath via the exports map's `types` fields | runs `engine` at generation time (probing), reads `data`'s schema | consumers' IDE/TS — no runtime dependency |
266
+ | `components` | `<SettingsPanel/>` (Shadow DOM) | `/components` | `data` (schema + UI tree), `engine`'s `/toggle` | host apps |
267
+ | `viewer` | `<Viewport/>` (three.js, Shadow DOM) | `/viewer` + `/viewer/toolpath` `/loaders` `/gcode` | `engine`'s `/settings`, the worker, `/data`; `three` (peer) | host apps |
268
+
269
+ Some of the connections are not imports, and knowing which is which explains most of the layout:
270
+
271
+ | Connection | Mechanism | Notes |
272
+ | --- | --- | --- |
273
+ | `wasm-core` → `engine` | `build.sh` (emscripten) commits its output into `engine/src/` | not an import — the golden byte-identical gate and the invariant suite verify kernel changes against the engine's output |
274
+ | `data` → `engine`/`components`/`viewer` | `/data` subpath imports | the source of truth is `extract_all.py` over the upstream checkout |
275
+ | `types` → `engine` | the generator **executes** `deriveKernelParams` (probing) and writes `engine/PARAMS.md` + `engine/src/kernel_setting_keys.js` back into the engine | `test_kernel_params.mjs` is the staleness gate |
276
+ | `types` → `data` | `gen_settings_types.mjs` generates `settings-keys.d.ts` (976 keys) from the schema | run automatically by the build |
277
+ | `viewer` ↔ `components` | **no direct link** | the host shares one settings map with both as props, and mounts `<SettingsPanel embedded/>` into the viewer's panel slots |
278
+ | inside `viewer` | `core` (pure, node-runnable) ← `scene` (three/DOM) ← `actions` ← `ui` | `test_layers.mjs` enforces the boundary |
279
+
212
280
  ## TypeScript
213
281
 
214
- Type declarations ship with the package — no `@types/*` needed. All 923 setting keys are typed from the config schema, enum values included:
282
+ Type declarations ship with the package — no `@types/*` needed. All 976 setting keys are typed from the config schema, enum values included:
215
283
 
216
284
  ```ts
217
285
  import { createSlicer, type SlicerSettings } from 'three-slicer'
@@ -252,7 +320,7 @@ const params = deriveKernelParams(settings)
252
320
  const result = slicer.slice(stlArrayBuffer, params)
253
321
  ```
254
322
 
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.
323
+ `deriveKernelParams()` maps the curated set of schema keys currently supported by the kernel — 131 of its 159 parameters are reachable from 128 schema keys today; the [generated reference](engine/PARAMS.md) is the authoritative list. Other schema keys can still be displayed by the UI, but they may not affect slicing output yet — `ignoredKernelSettings(settings)` names the keys of your map in that category, so a "why did this setting do nothing" question has a one-call answer. Vector options are simplified to their first element, except the filament options described below, which keep every extruder's entry.
256
324
 
257
325
  ## 3MF Projects
258
326
 
@@ -358,13 +426,10 @@ pick's leftovers behind — an ABS material followed by a PLA one keeps ABS's ch
358
426
  exposes the exact key set to remove first:
359
427
 
360
428
  ```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
- }
429
+ import { applyPreset } from 'three-slicer/settings'
366
430
 
367
431
  setSettings(s => applyPreset(s, filaments.settingsFor(name), filaments.keys))
432
+ // clears filaments.keys first, then merges; a null preset (failed lookup) applies nothing
368
433
  ```
369
434
 
370
435
  The same applies to `processPresets().keys` and `printerKeys`. The filament key set is disjoint from the process key
@@ -386,6 +451,8 @@ The kernel reloads the loaded-filament settings at every tool change and reports
386
451
 
387
452
  A single-material slice sends none of these keys and produces exactly the G-code it produced before multi-material existed.
388
453
 
454
+ Besides per-extruder arrays and painting, a per-feature filament assignment is the third way a slice becomes multi-tool: `outer_wall_filament_id`, `inner_wall_filament_id`, `sparse_infill_filament_id`, `top_surface_filament_id`, `bottom_surface_filament_id` and `internal_solid_filament_id` are 1-based filament indices ("print the outer wall with filament 2"). Values of 0 (default) and 1 (the default extruder) add no second tool, so existing single-material slices are unaffected.
455
+
389
456
  ### Painting a region onto another extruder
390
457
 
391
458
  Painting is the only way a single object can print in two materials. Painting states follow OrcaSlicer's own enum, in which state `1` is both the support enforcer and Extruder 1, state `2` is both the support blocker and Extruder 2, and `3..16` are Extruders 3 to 16. The state-addressed protocol is on the worker; `slicer.paint()` on the direct handle is the original enforcer/blocker boolean pair.
@@ -398,12 +465,12 @@ worker.postMessage({ cmd: 'erase', facet, hx, hy, hz, cx, cy, cz, radius })
398
465
 
399
466
  `erase` is a separate command rather than `state: 0`, because a JS `false` coerces to `0` at the WASM boundary and the legacy blocker brush sends exactly that — so the state path refuses `0` outright.
400
467
 
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.
468
+ Painting and support now coexist: the painted multi-material path runs the same support pass the single-material path does, so enabling support no longer costs the material paint (or the other way round). What remains is the ambiguity a single enum creates — a support **blocker** paint and an Extruder 2 paint are the same mark on a facet, so on a model that uses both brushes the blocker reads as "print with T1".
402
469
 
403
470
  ## SLA (Resin) Slicing
404
471
 
405
472
  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
473
+ apply an SLA machine profile from the catalog (`printerTechByVendor` marks the technology) or set the key
407
474
  yourself, then derive SLA params and call the SLA entry:
408
475
 
409
476
  ```js
@@ -481,7 +548,11 @@ and its out-of-memory retry ladder, both of which are UI policy rather than prot
481
548
  ### Driving the worker directly
482
549
 
483
550
  `createSlicerClient` is a thin wrapper; the protocol underneath is a plain message contract, fully typed in
484
- `three-slicer/worker`.
551
+ `three-slicer/worker`. A slice request is the message with **no `cmd`**, and its `params` takes an object or a
552
+ JSON string (object accepted since 0.2.3 — the kernel parses JSON text, so it is stringified for you).
553
+
554
+ The worker below is created with `engineWorkerURL()`, which suits a no-bundler page; under Vite or webpack
555
+ create it the way [Bundler Notes](#bundler-notes) describes and drive that instance instead.
485
556
 
486
557
  ```js
487
558
  import { engineWorkerURL } from 'three-slicer'
@@ -546,6 +617,19 @@ progress cannot be interrupted — terminate the worker instead.
546
617
 
547
618
  ## Bundler Notes
548
619
 
620
+ **Creating the slice worker.** `createSlicerClient()` with no argument is the supported path under Vite and
621
+ webpack: the default is written as the literal `new Worker(new URL('./slicer.worker.js', import.meta.url),
622
+ { type: 'module' })` that both bundlers recognize as a worker entry, so the worker reaches `dist` together
623
+ with the kernel chunks it dynamically imports. Verified in `pack_check.sh`, which builds a Vite consumer and
624
+ asserts every relative import the emitted worker chunk makes names a file the build actually produced.
625
+
626
+ Through 0.2.2 that default went through `engineWorkerURL()`, and a function call does not match the pattern —
627
+ Vite fell back to its plain-asset rule and copied the worker source in verbatim, still importing an unhashed
628
+ `./slicer_core.js`. Measured on a Vite 5 app importing `three-slicer/client` and nothing else: a 21KB copy in
629
+ a 40KB `dist` — no kernel emitted at all, a 404 on the worker's first message, and only after `vite build`,
630
+ since the dev server serves the sources. `engineWorkerURL()` remains for no-bundler setups (native ESM, an
631
+ import map, a CDN); under a bundler use the no-argument client or `three-slicer/worker?worker`.
632
+
549
633
  Vite consumers should enable ES module workers and an ES2022 build target:
550
634
 
551
635
  ```js
@@ -596,11 +680,12 @@ The package includes extracted OrcaSlicer metadata for custom interfaces:
596
680
 
597
681
  | File | Use |
598
682
  | --- | --- |
599
- | `three-slicer/data/config-schema.json` | Setting definitions, defaults, labels, units, and option metadata (923 options) |
683
+ | `three-slicer/data/config-schema.json` | Setting definitions, defaults, labels, units, and option metadata (976 options) |
600
684
  | `three-slicer/data/ui-tree.json` | Tab, page, group, and option layout |
601
685
  | `three-slicer/data/toggle-rules.json` | Original enable/disable rule metadata |
602
686
  | `three-slicer/data/invalidation-map.json` | Setting invalidation/dependency metadata |
603
- | `three-slicer/data/printers.json` | 1,035 vendor machine profiles across 64 vendors: motion limits, bed, nozzle |
687
+ | `three-slicer/data/preset-keys.json` | The option keys belonging to each preset type — what `presetOptionKeys(type)` reads |
688
+ | `three-slicer/data/printers.json` | 1,041 vendor machine profiles across 66 vendors: motion limits, bed, nozzle — plus the resin catalog and each vendor's printer technology |
604
689
  | `three-slicer/data/processes.js` | 2,243 print presets (speeds, accelerations) |
605
690
  | `three-slicer/data/filaments.js` | 5,999 material presets over 81 filament types, plus each printer model's recommended list |
606
691
 
@@ -623,13 +708,16 @@ the layout by hand.
623
708
  | `slicer.overlay(enforcer)` | The painted overlay triangles for one state, as a `Float32Array` |
624
709
  | `slicer.heapSize()` | Current WASM heap size, bytes (peak, monotonic) |
625
710
  | `slicer.dispose()` | Releases the slicer handle for garbage collection |
626
- | `engineWorkerURL()` | Returns a browser worker URL |
711
+ | `engineWorkerURL()` | Worker URL for a **no-bundler** browser setup. Under Vite/webpack use `createSlicerClient()` or `three-slicer/worker?worker` — see [Bundler Notes](#bundler-notes) |
627
712
 
628
713
  ### `three-slicer/settings`
629
714
 
630
715
  | API | Description |
631
716
  | --- | --- |
632
717
  | `deriveKernelParams(settings, opts?)` | Converts sparse OrcaSlicer settings to kernel params. `opts.plate` picks a plate's entry from per-plate options |
718
+ | `applyPreset(settings, preset, keys)` | Clear-then-merge: deletes the catalog's `keys`, merges the preset; a null preset applies nothing |
719
+ | `kernelSettingKeys` | The schema keys `deriveKernelParams` consumes — generated with the [reference table](engine/PARAMS.md) |
720
+ | `ignoredKernelSettings(settings)` | The map's keys the FFF derivation does **not** read — accepted but without slicing effect |
633
721
  | `schemaDefault(key)` | The config-schema default for a key |
634
722
  | `settingRaw(settings, key)` | The map's value, or the schema default |
635
723
  | `settingScalar(settings, key)` | The same, reduced to a scalar — the first set entry of a per-extruder column |
@@ -640,6 +728,7 @@ the layout by hand.
640
728
  | `readPresetFile(raw, opts)` | A preset `.json` → a settings map; follows `inherits` via `resolveParent` |
641
729
  | `presetOptionKeys(type)` | The option keys belonging to `machine` / `process` / `filament` |
642
730
  | `printersByVendor` | Vendor → profile name → entry, for building a picker |
731
+ | `printerTechByVendor` | Vendor → printer technology (`'FFF'` / `'SLA'`) |
643
732
  | `printerSettings(name)` | Settings a vendor machine profile applies |
644
733
  | `printerKeys` | Every key a printer profile can set — clear these before applying another |
645
734
  | `printerDefaultPreset(name)` | The vendor's recommended process preset for that printer |
@@ -678,7 +767,7 @@ the layout by hand.
678
767
 
679
768
  | API | Description |
680
769
  | --- | --- |
681
- | `schema`, `uiTree`, `toggleRules`, `invalidationMap`, `printers` | The extracted metadata, import attributes already applied |
770
+ | `schema`, `uiTree`, `toggleRules`, `invalidationMap`, `printers`, `presetKeys` | The extracted metadata, import attributes already applied |
682
771
  | `loadProcesses()` / `loadFilaments()` | The two large catalogs, on demand — prefer the `settings` facades |
683
772
 
684
773
  ## Runtime Support
@@ -691,6 +780,41 @@ the layout by hand.
691
780
  | React | Viewer and settings panel |
692
781
  | three.js | Required by `three-slicer/viewer` |
693
782
 
783
+ ## Before You Ship
784
+
785
+ The traps below were all hit while building the [integration demos](https://github.com/kimgh06/Web_Three_Slicer/tree/main/examples)
786
+ and the live landing embed — each one returns something plausible instead of an error, which is why they
787
+ are listed **in the order they bite**, not by topic. The later a trap fires, the more it costs — and none of
788
+ them fails a build or throws, so nothing surfaces them for you.
789
+
790
+ 1. **Your first real model: hand it over origin-centered, not bed-centered.** The kernel takes plate-local
791
+ coordinates and seats the model on the bed itself; pre-centering on the bed adds the offset twice and the
792
+ part slices *off the bed* — with plausible time and material and no error. Since 0.2.3 the result says so:
793
+ `result.warnings` carries `'over_bed_model'` (the `stats.over_bed_model` flag is still there too). Check
794
+ `result.warnings.length` after every slice; it is the channel every future condition of this kind uses.
795
+ 2. **Reading the results: `filament_mm` is a length, and the time depends on the kernel.** Grams are your
796
+ conversion (`filament_mm × π × (diameter/2)² × density / 1000`, from the filament preset's
797
+ `filament_diameter`/`filament_density`). `time_estimate` differs ~25% between the single- and
798
+ multi-threaded kernels on identical geometry, so your COOP/COEP headers change the number — pin one as
799
+ the reference if you price on it.
800
+ 3. **Drawing toolpaths: the role field is encoded, and G-code round-trips lose roles.** `paths[k+3]` holds
801
+ `role + tool*16` — mask with `& 15` / `>>> 4`. `SegmentData.position` is stride 4 (x, y, z, w). The
802
+ kernel's G-code carries no `;TYPE:` comments, so `parseGcode` collapses roles into wall — draw
803
+ `result.layers` directly when the slicing side is yours.
804
+ 4. **Settings: presets are sparse, and unread keys are silent.** Apply presets with
805
+ [`applyPreset`](#materials-and-multi-material) (clear-then-merge — a plain merge keeps the previous
806
+ material's leftovers). A key the FFF derivation does not read is accepted and does nothing:
807
+ `ignoredKernelSettings(settings)` names them.
808
+ 5. **Cancel is conditional.** `client.cancel()` works only on a cross-origin-isolated page (the mt kernel);
809
+ otherwise it returns `false` — terminate and recreate the client instead.
810
+ Details: [Cancelling a slice](#cancelling-a-slice).
811
+
812
+ Gone as of 0.2.3, and listed here only because it bit every consumer before then: `createSlicerClient()` with
813
+ no argument used to build a worker Vite copied as a plain asset, which 404'd on its own kernel import in
814
+ production while `npm run dev` passed. The no-argument form now emits a real worker chunk — see
815
+ [Bundler Notes](#bundler-notes). On 0.2.2 or earlier, pass the worker in yourself
816
+ (`import SlicerWorker from 'three-slicer/worker?worker'`).
817
+
694
818
  ## Known Limits
695
819
 
696
820
  - `slice()` currently takes binary STL input at the engine API boundary.
@@ -698,7 +822,7 @@ the layout by hand.
698
822
  - `registerLoader(exts, fn)` from `three-slicer/viewer/loaders` adds any other format. Formats needing a heavy dependency are kept out of the package so it stays runtime-dependency-free — see `web/viewer/src/step_loader.js` for a STEP loader built on `occt-import-js` (OCCT WASM).
699
823
  - Not every OrcaSlicer schema key is wired into the WASM kernel yet.
700
824
  - Some vector settings are simplified to their first element.
701
- - 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.
825
+ - One triangle selector serves both the support brush and the material brush (upstream's shared enum), so a support **blocker** paint and an Extruder 2 paint are the same mark — the two brushes cannot mark the same model independently.
702
826
  - The multi-material prime tower is a real ported wipe tower, but the fallback square ring used when it fails is not.
703
827
  - 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`.
704
828
  - The `.sl1` export lives in the viewer (its masks need a canvas); there is no headless `.sl1` writer export yet.
@@ -4,12 +4,12 @@ Reusable React components for the browser slicer. Props-driven — no global sta
4
4
 
5
5
  ## `<SettingsPanel/>`
6
6
 
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.
7
+ An OrcaSlicer-style settings form generated from `three-slicer/data` (schema + UI tree + toggle rules): tab/page/group navigation, mode filter (all/simple/advanced/expert/develop), search across 976 options, dirty markers with per-option reset, and enable/disable rules evaluation.
8
8
 
9
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
10
 
11
11
  ```bash
12
- npm i three-slicer # react is a peer dependency — npm installs it for you
12
+ npm i three-slicer react react-dom # react is an OPTIONAL peer — npm does not install it for you
13
13
  ```
14
14
 
15
15
  ```jsx
@@ -29,10 +29,13 @@ Props:
29
29
  | `settings` | `{ [schemaKey]: value }` | Sparse settings map — the consumer's state is the single source of truth |
30
30
  | `setSettings` | `(updater) => void` | React setState-style updater; the only way state leaves the component |
31
31
  | `onOptionOpen` | `(key) => void` (optional) | Label click hook for deep links/detail views. Omit → plain labels |
32
+ | `embedded` | `boolean` (optional) | Compact layout for embedding inside another panel |
33
+ | `customWidgets` | `{ [schemaKey]: Component }` (optional) | Replace schema-driven widgets per key (e.g. `{ printable_area: MyBedEditor }`) |
34
+ | `only` | `{ builder, page? }` (optional) | Pins the panel to one builder/page and drops the search/page/mode chrome — how `<Viewport/>`'s `motionPanel` slot is meant to be filled |
32
35
 
33
36
  The same `settings` object feeds `deriveKernelParams()` in `three-slicer` and the `<Viewport/>` in `three-slicer/viewer` — one state connects all three.
34
37
 
35
- Ships transpiled ESM (`dist/`); react is a peer dependency. Styling is class-based and unstyled by default (bring your own CSS, or reuse the demo's).
38
+ Ships transpiled ESM (`dist/`); react is an optional peer dependency. The panel arrives fully styled: its stylesheet is inlined into the bundle and injected into a Shadow DOM root, so host CSS neither styles it nor collides with it.
36
39
 
37
40
  The 3D viewport lives in its own package: `three-slicer/viewer`.
38
41
 
package/data/README.md CHANGED
@@ -4,12 +4,13 @@ OrcaSlicer configuration metadata extracted from the original C++ sources, shipp
4
4
 
5
5
  | File | Contents |
6
6
  |---|---|
7
- | `config-schema.json` | 923 print options: type, label, category, tooltip, min/max, mode, enum values/labels, defaults, source line numbers |
8
- | `config-schema-builddump.json` | Build-verified cross-check: the real `print_config_def` (817 options) dumped from a WASM compile of the original `PrintConfig.cpp` |
9
- | `ui-tree.json` | Tab → page → group → option tree from `Tab.cpp` (20 pages, 542 option references) |
7
+ | `config-schema.json` | 976 print options: type, label, category, tooltip, min/max, mode, enum values/labels, defaults, source line numbers |
8
+ | `config-schema-builddump.json` | Build-verified cross-check: the real `print_config_def` (817 options) dumped from a WASM compile of the original `PrintConfig.cpp` — repo-only, excluded from the npm tarball |
9
+ | `ui-tree.json` | Tab → page → group → option tree from `Tab.cpp` (34 pages, 618 option references — the SLA tabs included) |
10
10
  | `toggle-rules.json` | 231 enable/disable rules with original C++ `enable_if` condition source |
11
11
  | `invalidation-map.json` | Option-change → re-slice step mapping (Print/PrintObject invalidation branches) |
12
- | `printers.json` | 1,035 vendor machine profiles across 64 vendors: motion limits, bed and nozzle |
12
+ | `preset-keys.json` | The option keys belonging to each preset type (`machine`/`process`/`filament`) — what `presetOptionKeys(type)` reads |
13
+ | `printers.json` | 1,041 vendor machine profiles across 66 vendors: motion limits, bed and nozzle — plus the resin catalog and each vendor's printer technology |
13
14
  | `processes.js` | 2,243 print presets (speeds, accelerations), joined to printers by `compatible_printers` |
14
15
  | `filaments.js` | 5,999 material presets over 81 filament types, plus each printer model's recommended list |
15
16
 
@@ -18,10 +19,10 @@ The last two are column-oriented and deduplicated, and load on demand. Read them
18
19
 
19
20
  ## Material presets
20
21
 
21
- `filaments.js` carries only the keys the kernel actually reads (14 today: temperatures, flow, diameter, cooling,
22
- and the retraction/z-hop overrides a material is allowed to apply on top of the machine's). That column set is
23
- disjoint from `processes.js`' 59, which is what lets a material pick and a process pick be applied independently —
24
- neither clears the other's values.
22
+ `filaments.js` carries only the keys the kernel actually reads (18 today: temperatures, flow, diameter, cooling,
23
+ pressure advance, cost/density, and the retraction/z-hop overrides a material is allowed to apply on top of the
24
+ machine's). That column set is disjoint from `processes.js`' 85, which is what lets a material pick and a process
25
+ pick be applied independently — neither clears the other's values.
25
26
 
26
27
  `filamentPresets()` is the facade:
27
28
 
@@ -37,7 +38,7 @@ filaments.keys // clear these before appl
37
38
 
38
39
  `recommendedFor()` intersects with the compatible list rather than returning the raw recommendation, and the
39
40
  filtering is not cosmetic: the recommendation is declared on the machine **model** and so is nozzle-agnostic,
40
- while its entries name nozzle-specific presets. Across all 1,035 printer profiles, 1,749 of 4,259 raw
41
+ while its entries name nozzle-specific presets. Across all 1,041 printer profiles, 1,749 of 4,259 raw
41
42
  recommendation entries (41%) name a material whose own compatible list excludes that profile.
42
43
 
43
44
  `type` and `vendor` are empty strings when the profile chain declares neither — 265 of the 5,999 presets. That is