three-slicer 0.1.5 → 0.1.7

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
@@ -4,6 +4,8 @@ Browser/WASM 3D-printing slicer package derived from [OrcaSlicer](https://github
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
 
7
+ ![A sliced Benchy in the viewer's Preview tab — organic tree supports, per-feature toolpath colors, dual layer-range slider, filament and print-time estimates](https://raw.githubusercontent.com/kimgh06/Web_Three_Slicer/main/web/viewer/public/usage.png)
8
+
7
9
  ## Links
8
10
 
9
11
  - Package: [npmjs.com/package/three-slicer](https://www.npmjs.com/package/three-slicer)
@@ -19,6 +21,7 @@ The package is published as a single npm package, `three-slicer`, with subpath e
19
21
  - React `<SettingsPanel/>` generated from extracted OrcaSlicer metadata.
20
22
  - Vendor printer, print-process and filament preset catalogs, with one material assignable per extruder.
21
23
  - Support painting and material painting, plus per-tool filament and purge statistics.
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.
22
25
  - Extracted data files for custom UIs: config schema, UI tree, toggle rules, invalidation map, and the printer/process/filament catalogs.
23
26
  - Automatic multithreaded WASM selection on cross-origin-isolated browser pages.
24
27
 
@@ -28,14 +31,18 @@ The package is published as a single npm package, `three-slicer`, with subpath e
28
31
  npm i three-slicer
29
32
  ```
30
33
 
31
- `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`.
34
+ `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
+ Install them yourself when you use the viewer or the settings panel:
32
37
 
33
- Only the viewer and components import them. If you use the headless engine, they are installed into `node_modules` but never enter your bundle:
38
+ ```bash
39
+ npm i three-slicer react react-dom three
40
+ ```
34
41
 
35
- | Import path | Needs react/three at runtime |
42
+ | Import path | Needs react/three |
36
43
  | --- | --- |
37
- | `three-slicer`, `/settings`, `/toggle`, `/worker`, `/wasm`, `/data` | no |
38
- | `three-slicer/viewer`, `/viewer/toolpath`, `/viewer/loaders` | yes |
44
+ | `three-slicer`, `/settings`, `/toggle`, `/client`, `/worker`, `/wasm`, `/data` | no |
45
+ | `three-slicer/viewer`, `/viewer/toolpath`, `/viewer/loaders`, `/viewer/gcode` | yes |
39
46
  | `three-slicer/components` | react only |
40
47
 
41
48
  ## Quick Start: Headless Slicing
@@ -63,7 +70,11 @@ console.log(result.gcode)
63
70
  slicer.dispose()
64
71
  ```
65
72
 
66
- `slice()` accepts a binary STL as an `ArrayBuffer` or `Uint8Array`. Parameters can be either a kernel params object or a JSON string.
73
+ `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.
74
+
75
+ 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.
76
+
77
+ `result.error` is set instead of a result when a slice fails or is cancelled — check it before reading `gcode`.
67
78
 
68
79
  ## Streaming Layers
69
80
 
@@ -99,15 +110,22 @@ export default function App() {
99
110
  const [settings, setSettings] = useState({})
100
111
 
101
112
  return (
102
- <Viewport
103
- settings={settings}
104
- setSettings={setSettings}
105
- />
113
+ <div style={{ position: 'relative', width: '100%', height: '100vh' }}>
114
+ <Viewport
115
+ settings={settings}
116
+ setSettings={setSettings}
117
+ />
118
+ </div>
106
119
  )
107
120
  }
108
121
  ```
109
122
 
110
- 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()`. Viewer and component styles are bundled into their Shadow DOM roots, so host app CSS does not need to import package CSS.
123
+ **The container is not optional.** The viewer fills its nearest *positioned* ancestor — the shadow host is
124
+ `display: contents` and the shell inside is `position: absolute; inset: 0` — so it needs a parent with
125
+ `position: relative` and a real height. In a plain static `<div>` it escapes to the page instead. There is no width
126
+ or height prop.
127
+
128
+ 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.
111
129
 
112
130
  ## Quick Start: Settings Panel
113
131
 
@@ -178,12 +196,14 @@ Available subpaths:
178
196
  | `three-slicer` | Headless WASM engine SDK |
179
197
  | `three-slicer/settings` | Schema defaults and settings-to-kernel mapping |
180
198
  | `three-slicer/toggle` | Enable/disable rule evaluation for settings UIs |
181
- | `three-slicer/worker` | Browser worker entry |
199
+ | `three-slicer/worker` | Browser worker entry, and the typed message protocol |
200
+ | `three-slicer/client` | `createSlicerClient()` — promises over that protocol |
182
201
  | `three-slicer/wasm` | Single-threaded Emscripten WASM glue |
183
202
  | `three-slicer/wasm-mt` | Multithreaded Emscripten WASM glue |
184
203
  | `three-slicer/viewer` | React `<Viewport/>` |
185
204
  | `three-slicer/viewer/toolpath` | GPU toolpath renderer utilities |
186
205
  | `three-slicer/viewer/loaders` | Model loaders |
206
+ | `three-slicer/viewer/gcode` | `parseGcode(text)` — G-code back into the layer stream the renderer consumes |
187
207
  | `three-slicer/components` | React `<SettingsPanel/>` |
188
208
  | `three-slicer/data` | Named exports for extracted metadata |
189
209
  | `three-slicer/data/*.json` | Extracted OrcaSlicer metadata |
@@ -233,6 +253,91 @@ const result = slicer.slice(stlArrayBuffer, params)
233
253
 
234
254
  `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.
235
255
 
256
+ ## 3MF Projects
257
+
258
+ A slicer-written `.3mf` is a project, not a mesh file: `Metadata/project_settings.config` holds the flattened
259
+ preset its author sliced with. `<Viewport/>` reads all of it on import, and the two codecs behind that are exported
260
+ for hosts doing it themselves.
261
+
262
+ **Every value in that file is a string**, and reading it raw is not a cosmetic problem — a disabled option is the
263
+ string `"0"`, and `"0"` is truthy, so a raw import turns every disabled option ON. A point is the string `"256x256"`
264
+ while every consumer indexes it as `[x, y]`, so a raw `printable_area[1][0]` is the character `'2'`. Both are
265
+ handled by coercing each value by its config-schema type:
266
+
267
+ ```js
268
+ import { normalizeProjectSettings, serializeProjectSettings } from 'three-slicer/settings'
269
+
270
+ const { settings, applied, skipped } = normalizeProjectSettings(rawConfig)
271
+ // settings: real JS types, ready for deriveKernelParams() and <SettingsPanel/>
272
+ // applied : how many keys survived
273
+ // skipped : keys the config schema does not define (preset bookkeeping, version fields) — dropped, not coerced
274
+ ```
275
+
276
+ Writing one back is the exact inverse, and it matters for the same reason: a raw JS `false` would be read by any
277
+ other slicer's parser as the string `"false"`, which is truthy.
278
+
279
+ ```js
280
+ const config = serializeProjectSettings(settings) // bool -> "1"/"0", point -> "XxY", points group -> "X1xY1,X2xY2"
281
+ ```
282
+
283
+ Upstream's `inherits` / `different_settings_to_system` reconciliation is deliberately not reproduced: it exists to
284
+ rebase a stored preset onto a local vendor preset database, and `project_settings.config` is already flattened, so
285
+ its values are taken as written.
286
+
287
+ ## Preset Files
288
+
289
+ OrcaSlicer's "Config files" dialog imports a `.json` holding one preset — a printer (`machine`), print settings
290
+ (`process`) or a material (`filament`) — plus the zip forms `.orca_printer` / `.orca_bundle` / `.orca_filament`.
291
+ Both directions are supported.
292
+
293
+ ```js
294
+ import { writePresetFile, readPresetFile, presetOptionKeys, printerSettings } from 'three-slicer/settings'
295
+
296
+ // Write. Flattened on purpose: every option of that type, no `inherits`, so the file stands on its own.
297
+ const file = writePresetFile(settings, { type: 'machine', name: 'My printer' })
298
+ // -> { type: 'machine', name: 'My printer', from: 'User', printable_area: ['0x0','256x0',…], … }
299
+
300
+ // Read. `resolveParent` follows `inherits`; without it a vendor file arrives incomplete (see below).
301
+ const { settings: loaded, missingParent, skipped } = readPresetFile(file,
302
+ { resolveParent: (name) => printerSettings(name) })
303
+ ```
304
+
305
+ `presetOptionKeys(type)` is the key set for one type — 175 for `machine`, 370 for `process`, 149 for `filament`,
306
+ taken from upstream's own `Preset::printer_options()` and friends. It is what splits a settings map, which holds
307
+ every type's keys at once, into the file for one of them.
308
+
309
+ **A vendor preset is a diff, not a preset.** Upstream saves a derived preset as the difference against its parent
310
+ (`Preset::save`: *"only save difference if it has parent"*), so a file off the vendor profiles carries a fraction
311
+ of what it appears to. Read Bambu's X1C machine file without resolving `inherits` and it has no `printable_area`
312
+ and no `printable_height` at all — the bed is in the parent. `missingParent` names the parent when it could not be
313
+ resolved; the file's own values are still applied, because dropping the file entirely loses more than it protects.
314
+
315
+ Values are written and read in upstream's own encoding — every value a string, a point as `"XxY"`, per-extruder
316
+ options as arrays of strings — the same shape [3MF Projects](#3mf-projects) uses, since upstream writes both
317
+ through the same serializer.
318
+
319
+ The viewer adds the zip forms and a UI: the printer card's **Load** and **Save** read and write these files, and a
320
+ save routes through [`onExport`](viewer/README.md#taking-the-saves) like every other download.
321
+
322
+ ## Settings UI: Enable/Disable Rules
323
+
324
+ OrcaSlicer greys out options that another option makes irrelevant. Those rules are extracted, and
325
+ `three-slicer/toggle` evaluates them against a settings map — the panel does this internally, and a custom UI can
326
+ do the same:
327
+
328
+ ```js
329
+ import { makeCfg, disabledKeys } from 'three-slicer/toggle'
330
+
331
+ const disabled = disabledKeys(makeCfg(settings))
332
+ // setting key -> the enable_if expression that evaluated false, e.g.
333
+ // { outer_wall_jerk: 'false, variant_index', … }
334
+ if (disabled.outer_wall_jerk) { /* grey the control out; the value says why */ }
335
+ ```
336
+
337
+ Only rules that evaluate to an unambiguous `false` are reported — an expression referring to something the
338
+ evaluator cannot resolve is left out rather than guessed at, so a key missing from the map means "not known to be
339
+ disabled", not "definitely enabled".
340
+
236
341
  ## Materials and Multi-Material
237
342
 
238
343
  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:
@@ -246,7 +351,22 @@ filaments.recommendedFor('Bambu Lab X1 Carbon 0.4 nozzle') // the vendor's sho
246
351
  const material = filaments.settingsFor('Bambu PLA Basic @BBL X1C')
247
352
  ```
248
353
 
249
- The filament key set is disjoint from the process key set, so applying a material never clears a process pick and vice versa.
354
+ **Clear before you apply.** A preset carries only the keys it sets, so merging one over another leaves the previous
355
+ pick's leftovers behind — an ABS material followed by a PLA one keeps ABS's chamber temperature. Every facade
356
+ exposes the exact key set to remove first:
357
+
358
+ ```js
359
+ function applyPreset(settings, preset, keys) {
360
+ const next = { ...settings }
361
+ for (const key of keys) delete next[key]
362
+ return Object.assign(next, preset)
363
+ }
364
+
365
+ setSettings(s => applyPreset(s, filaments.settingsFor(name), filaments.keys))
366
+ ```
367
+
368
+ The same applies to `processPresets().keys` and `printerKeys`. The filament key set is disjoint from the process key
369
+ set, so applying a material never clears a process pick and vice versa.
250
370
 
251
371
  **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:
252
372
 
@@ -280,13 +400,49 @@ Because a single enum serves both jobs, a support blocker paint and an Extruder
280
400
 
281
401
  ## Worker Usage
282
402
 
283
- Use a module worker to run slicing off the browser main thread.
403
+ Slicing blocks its thread for seconds, so in a browser it belongs in a worker. `three-slicer/client` wraps the
404
+ worker's message protocol in promises:
405
+
406
+ ```js
407
+ import { createSlicerClient } from 'three-slicer/client'
408
+ import { deriveKernelParams } from 'three-slicer/settings'
409
+
410
+ const client = createSlicerClient() // creates and owns the worker
411
+ await client.warmup() // optional: load the kernel before the first slice needs it
412
+
413
+ const { gcode, stats } = await client.slice(stlArrayBuffer, deriveKernelParams(settings), {
414
+ onProgress: (done, total) => setProgress(done / total),
415
+ })
416
+
417
+ client.cancel() // stops a running slice — see below
418
+ client.terminate()
419
+ ```
420
+
421
+ Pass `onLayer` to take the layers as they are produced instead of having them assembled, which is what keeps a
422
+ large model out of memory:
423
+
424
+ ```js
425
+ await client.slice(stl, params, { onLayer: ({ z, idx, gcode }) => append(gcode) })
426
+ // the result then carries stats only
427
+ ```
428
+
429
+ The same client drives painting — `prepare`, `paint`, `erase`, `clear`, `overlay`, `importPaint`, `exportPaint`.
430
+ It deliberately leaves two things to the host: the viewer's progress weighting (measured against its own models)
431
+ and its out-of-memory retry ladder, both of which are UI policy rather than protocol.
432
+
433
+ ### Driving the worker directly
434
+
435
+ `createSlicerClient` is a thin wrapper; the protocol underneath is a plain message contract, fully typed in
436
+ `three-slicer/worker`.
284
437
 
285
438
  ```js
286
439
  import { engineWorkerURL } from 'three-slicer'
440
+ import { deriveKernelParams } from 'three-slicer/settings'
287
441
 
288
442
  const worker = new Worker(engineWorkerURL(), { type: 'module' })
289
443
 
444
+ const chunks = []
445
+
290
446
  worker.onmessage = (event) => {
291
447
  const message = event.data
292
448
 
@@ -295,20 +451,50 @@ worker.onmessage = (event) => {
295
451
  }
296
452
 
297
453
  if (message.type === 'layer') {
298
- console.log(message.idx, message.z, message.gcode)
454
+ chunks.push(message.gcode) // paths/widths arrive transferred, not copied
299
455
  }
300
456
 
301
457
  if (message.type === 'done') {
302
- console.log(message.stats)
458
+ console.log(chunks.join(''), message.result.stats)
303
459
  }
304
460
 
305
461
  if (message.type === 'error') {
306
462
  console.error(message.error)
307
463
  }
308
464
  }
465
+
466
+ // A slice request carries NO `cmd` — it is the worker's default action. Every other message (paint, prepare,
467
+ // overlay, …) is addressed by `cmd`, so leaving it off is what selects slicing.
468
+ // `params` must be a JSON STRING here. The direct handle's slice() also accepts an object and stringifies it for
469
+ // you; the worker hands its argument straight to the kernel, which parses JSON text.
470
+ worker.postMessage({ stl: stlArrayBuffer, params: JSON.stringify(deriveKernelParams(settings)) }, [stlArrayBuffer])
309
471
  ```
310
472
 
311
- The worker protocol emits `progress`, `layer`, `done`, and `error` messages. For bundler-specific setups, `three-slicer/worker` exposes the worker entry directly.
473
+ The worker replies with `progress`, `layer`, `done`, and `error` while slicing. `done` carries `{result}` — its
474
+ `stats` when the layers were streamed, plus `gcode` and `layers` when they were not. For bundler-specific setups,
475
+ `three-slicer/worker` exposes the worker entry directly.
476
+
477
+ ### Cancelling a slice
478
+
479
+ Cancellation is a flag the kernel polls from inside its C++ loop, so it works even while the worker is blocked in
480
+ WASM. It lives in a `SharedArrayBuffer`, which means it is only available on the multithreaded kernel — that is, on
481
+ a [cross-origin isolated](#multithreaded-wasm) page. `client.cancel()` handles this and returns `false` when there
482
+ is no flag to write. Driving the worker yourself, the address arrives once, unprompted:
483
+
484
+ ```js
485
+ let cancelView = null
486
+
487
+ worker.addEventListener('message', ({ data }) => {
488
+ if (data.type === 'supsab' && data.cancelPtr) {
489
+ cancelView = new Uint32Array(data.buf, data.cancelPtr, 1)
490
+ }
491
+ })
492
+
493
+ const cancel = () => cancelView && Atomics.store(cancelView, 0, 1)
494
+ ```
495
+
496
+ Without cross-origin isolation the engine runs single-threaded, no `supsab` message arrives, and a slice in
497
+ progress cannot be interrupted — terminate the worker instead.
312
498
 
313
499
  ## Bundler Notes
314
500
 
@@ -376,23 +562,72 @@ the layout by hand.
376
562
 
377
563
  ## API Reference
378
564
 
565
+ ### `three-slicer`
566
+
379
567
  | API | Description |
380
568
  | --- | --- |
381
569
  | `createSlicer()` | Loads the WASM kernel and returns a slicer handle |
382
570
  | `slicer.slice(stl, params, callbacks?)` | Slices binary STL input to G-code or streamed layers |
383
- | `slicer.paintPrepare(stl)` | Prepares painting against a model |
571
+ | `slicer.paintPrepare(stl)` | Prepares painting against a model; returns the facet count |
384
572
  | `slicer.paint(args)` | Paints enforcer/blocker data (boolean pair; the state-addressed form is worker-only) |
385
573
  | `slicer.paintClear()` | Clears painting data for every state |
386
- | `slicer.overlay(enforcer)` | Returns painting overlay data |
387
- | `slicer.heapSize()` | Returns current WASM heap size |
574
+ | `slicer.overlay(enforcer)` | The painted overlay triangles for one state, as a `Float32Array` |
575
+ | `slicer.heapSize()` | Current WASM heap size, bytes (peak, monotonic) |
388
576
  | `slicer.dispose()` | Releases the slicer handle for garbage collection |
389
577
  | `engineWorkerURL()` | Returns a browser worker URL |
390
- | `deriveKernelParams(settings)` | Converts sparse OrcaSlicer settings to kernel params |
578
+
579
+ ### `three-slicer/settings`
580
+
581
+ | API | Description |
582
+ | --- | --- |
583
+ | `deriveKernelParams(settings, opts?)` | Converts sparse OrcaSlicer settings to kernel params. `opts.plate` picks a plate's entry from per-plate options |
584
+ | `schemaDefault(key)` | The config-schema default for a key |
585
+ | `settingRaw(settings, key)` | The map's value, or the schema default |
586
+ | `settingScalar(settings, key)` | The same, reduced to a scalar — the first set entry of a per-extruder column |
587
+ | `normalizeProjectSettings(raw)` | A 3mf's all-strings `project_settings.config` → a typed settings map |
588
+ | `serializeProjectSettings(settings)` | The inverse, for writing one |
589
+ | `writePresetFile(settings, opts)` | A settings map → an OrcaSlicer preset `.json`, flattened |
590
+ | `presetFileText(settings, opts)` | The same, stringified |
591
+ | `readPresetFile(raw, opts)` | A preset `.json` → a settings map; follows `inherits` via `resolveParent` |
592
+ | `presetOptionKeys(type)` | The option keys belonging to `machine` / `process` / `filament` |
593
+ | `printersByVendor` | Vendor → profile name → entry, for building a picker |
391
594
  | `printerSettings(name)` | Settings a vendor machine profile applies |
595
+ | `printerKeys` | Every key a printer profile can set — clear these before applying another |
596
+ | `printerDefaultPreset(name)` | The vendor's recommended process preset for that printer |
597
+ | `machineLimitKeys` | The schema keys the kernel's machine limits are read from |
392
598
  | `processPresets()` | Lazy facade over the print (process) preset catalog |
393
599
  | `filamentPresets()` | Lazy facade over the material preset catalog |
600
+
601
+ ### `three-slicer/toggle`
602
+
603
+ | API | Description |
604
+ | --- | --- |
605
+ | `makeCfg(settings)` | Wraps a settings map for rule evaluation |
606
+ | `disabledKeys(cfg)` | Setting key → the `enable_if` expression that evaluated false |
607
+ | `evalEnableIf(expr, locals, cfg)` | Evaluates a single rule; `null` when it cannot be resolved |
608
+
609
+ ### `three-slicer/viewer` and `three-slicer/components`
610
+
611
+ | API | Description |
612
+ | --- | --- |
394
613
  | `<Viewport/>` | React slicer viewport |
395
614
  | `<SettingsPanel/>` | React settings form |
615
+ | `loadModel(name, buffer)` | Parses STL/OBJ/3MF/AMF/PLY into objects |
616
+ | `registerLoader(exts, fn)` | Adds a format — STEP and anything else needing a heavy dependency |
617
+ | `SUPPORTED_EXT` / `fileExt(name)` | The known extensions, and one file's |
618
+ | `splitConnectedComponents(pos)` | Splits a mesh into its connected components; `null` when there is only one |
619
+ | `parseGcode(text)` | G-code text → the layer stream the renderer consumes |
620
+ | `buildSegmentData(layers, w)` | Layers → the packed segment buffers |
621
+ | `computeColors(data, view, ctx)` | Per-segment colours for a view type |
622
+ | `makeToolpath(THREE, data)` | The GPU toolpath mesh handle |
623
+ | `VIEW_TYPES` / `roleRatios(lengths)` | The preview's view types, and the per-role length split |
624
+
625
+ ### `three-slicer/data`
626
+
627
+ | API | Description |
628
+ | --- | --- |
629
+ | `schema`, `uiTree`, `toggleRules`, `invalidationMap`, `printers` | The extracted metadata, import attributes already applied |
630
+ | `loadProcesses()` / `loadFilaments()` | The two large catalogs, on demand — prefer the `settings` facades |
396
631
 
397
632
  ## Runtime Support
398
633
 
@@ -4,10 +4,10 @@ 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 (simple/advanced/expert), search across 923 options, dirty markers with per-option reset, and enable/disable rules evaluation.
8
8
 
9
9
  ```bash
10
- npm i three-slicer/components react
10
+ npm i three-slicer # react is a peer dependency — npm installs it for you
11
11
  ```
12
12
 
13
13
  ```jsx
package/data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # @three-slicer/data
1
+ # three-slicer/data
2
2
 
3
- OrcaSlicer configuration metadata extracted from the original C++ sources, as plain JSON. Used by `three-slicer` (defaults, settings mapping) and `three-slicer/components` (settings UI), and usable standalone for building slicer UIs.
3
+ OrcaSlicer configuration metadata extracted from the original C++ sources, shipped inside the `three-slicer` package as its `/data` subpath. Used by `three-slicer` (defaults, settings mapping) and `three-slicer/components` (settings UI), and usable standalone for building slicer UIs.
4
4
 
5
5
  | File | Contents |
6
6
  |---|---|