three-slicer 0.2.5 → 0.3.1

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 (44) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +13 -8
  3. package/THIRD-PARTY-NOTICES.md +7 -2
  4. package/components/dist/SettingsPanel.js +12 -198
  5. package/data/config-schema-lean.json +1 -0
  6. package/data/processes.js +1 -1
  7. package/data/ui-tree-keys.json +1 -0
  8. package/engine/src/client.js +9 -0
  9. package/engine/src/data.js +6 -0
  10. package/engine/src/settings.js +12 -627
  11. package/engine/src/sla_core.js +14 -8
  12. package/engine/src/toggle_eval.js +9 -77
  13. package/package.json +24 -17
  14. package/types/client.d.ts +3 -0
  15. package/types/components.d.ts +3 -29
  16. package/types/settings.d.ts +15 -0
  17. package/types/toggle.d.ts +2 -22
  18. package/types/viewer-gcode.d.ts +2 -29
  19. package/types/viewer-loaders.d.ts +1 -33
  20. package/types/viewer-toolpath.d.ts +3 -113
  21. package/types/viewer.d.ts +6 -213
  22. package/viewer/dist/Viewport.js +9 -6571
  23. package/viewer/dist/gcode_reexport.js +1 -0
  24. package/viewer/dist/loaders_reexport.js +1 -0
  25. package/viewer/dist/toolpath_reexport.js +1 -0
  26. package/components/README.md +0 -44
  27. package/components/styles.css +0 -88
  28. package/engine/src/kernel_setting_keys.js +0 -132
  29. package/engine/src/preset_file.js +0 -99
  30. package/types/.omc/state/sessions/02e5ddf5-4378-46e1-8fca-e90412c30804/pre-tool-advisory-throttle.json +0 -10
  31. package/viewer/README.md +0 -423
  32. package/viewer/dist/gcode_parse.js +0 -190
  33. package/viewer/dist/make_worker.js +0 -31
  34. package/viewer/dist/model_loaders.js +0 -184
  35. package/viewer/dist/parse_3mf-C2X_W442.js +0 -300
  36. package/viewer/dist/parse_3mf.worker.js +0 -10
  37. package/viewer/dist/sl1_encode.worker.js +0 -10
  38. package/viewer/dist/sl1_write-CtX8cVL7.js +0 -302
  39. package/viewer/dist/sla_reconstruct-CXkYhux2.js +0 -176
  40. package/viewer/dist/sla_reconstruct.worker.js +0 -24
  41. package/viewer/dist/sla_slice.worker.js +0 -74
  42. package/viewer/dist/toolpath_gpu-4foc4FaU.js +0 -482
  43. package/viewer/dist/toolpath_gpu.js +0 -13
  44. package/viewer/styles.css +0 -394
package/CHANGELOG.md CHANGED
@@ -1,5 +1,139 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1 — 2026-09-16
4
+
5
+ ### Added
6
+
7
+ - `three-slicer-viewer/settings` (and so `three-slicer/settings`) exports the kernel derivation's own fallbacks:
8
+ `FFF_FALLBACKS`, `SLA_FALLBACKS`, `AUTO_LINE_WIDTH`, `BED_FALLBACK`, `MACHINE_LIMITS`. Frozen; they are the values
9
+ `deriveKernelParams` / `deriveSlaParams` use when neither the settings map nor the schema gives one, and they are
10
+ deliberately not the schema defaults.
11
+ - Theming: both shadow roots take their colours from 43 custom properties (`--vp-accent`, `--vp-dark-surface`,
12
+ `--vp-light-text`, …) a host can set on any ancestor. `VIEWER.md` ("Theming") lists them with their defaults.
13
+
14
+ ### Changed
15
+
16
+ - `deriveKernelParams` with an empty or malformed `printable_area` now derives a 200 x 200 bed (the schema's own
17
+ default), not 256 x 256. Every viewer fallback already assumed 200; a well-formed value is unaffected.
18
+ - `buildSegmentData(layers)` without a default line width falls back to 0.42 (the kernel's auto width), not 0.4.
19
+ Callers that pass one, as the viewer does, are unaffected.
20
+ - One grey for "no colour to read": a blank filament slot, a stats swatch past the palette, an unparseable hex in
21
+ the toolpath palette and an unpainted facet (`UNPAINTED_COLOR`) are all `#888888` (were four different greys).
22
+ - The toolpath legend (speed / fan / temperature views) falls back to the schema default for a value the settings
23
+ map holds but cannot parse, instead of literals that disagreed with it (support speed 35 vs 80, nozzle 210 vs 200).
24
+ - The stylesheets carried 152 literal colours, many of them near-identical shades of one role; each group now uses
25
+ its most common member, so a few shades move slightly. Visible: the "advanced" badge is the info blue
26
+ (`#2b6cff`, was `#3673c3`), the "simple" badge the accent green, and the prime-tower card's number inputs are
27
+ light like every other sidebar input (they were styled for the dark viewport).
28
+ - Vendor process presets now carry `support_type`, so applying one sets the support type it was authored with.
29
+
30
+ ### Fixed
31
+
32
+ - Importing a project that places objects on more than one plate failed with "dropPlateResult is not defined"
33
+ and left every object on the selected plate (since 0.2.5). `test_3mf_project.mjs` now loads a two-plate project
34
+ through the model loader.
35
+ - The Process card's Global|Plate toggle has the ↺ that drops the plate's whole override, like the other settings
36
+ cards; the viewer never passed it the handler.
37
+ - Importing a project kept its filament colours in the viewer but not in the settings map, so a blank entry saved
38
+ back as blank and a list past 16 filaments saved whole.
39
+ - `three-slicer@0.3.0` shipped a tool-state file under `types/.omc/`, and `three-slicer-viewer` would have shipped
40
+ one from `src/settings/.omc/`. Both `files` lists now end in `!**/.omc`, and `pack_check.sh` fails on any
41
+ dot-path in either tarball.
42
+
43
+ ## 0.3.0 — 2026-09-08
44
+
45
+ The viewer is its own package. Everything that displays — model loading, G-code parsing, the GPU toolpath
46
+ renderer, the settings transforms, `<Viewport/>` and `<SettingsPanel/>` — now ships as
47
+ [`three-slicer-viewer`](https://www.npmjs.com/package/three-slicer-viewer) under **MIT**, so it can go into
48
+ closed-source products. `three-slicer` stays AGPL and keeps every entry point it had: `three-slicer/viewer` and
49
+ `three-slicer/components` are now thin wrappers that plug the WASM kernel and the vendor catalog into the
50
+ permissive package. Which files could carry which license, and why, is `packages/PROVENANCE.md`; the plan the
51
+ split followed is `packages/RELICENSE.md`.
52
+
53
+ ### Added
54
+
55
+ - `three-slicer-viewer` (MIT): `<Viewport/>` with `slicer` and `catalog` props (defaults: a no-op slicer and an
56
+ empty catalog), `useSlicer({ makeWorker })` — the slicing hook with the kernel injected —, `/toolpath`,
57
+ `/gcode`, `/loaders`, `/settings`, `/data`, `/toggle`, `/components`, and the `.d.ts` for each.
58
+ - `makeSlicerWorker` in `three-slicer/client` and `bundledCatalog` in `three-slicer/settings`: the two things
59
+ that cannot be permissive, exported from where they live so a host can compose the viewer with the kernel
60
+ by hand (`web/viewer/src/Prepare.jsx` does).
61
+ - `three-slicer-viewer/toggle`: `makeToggle(rules, schema)` — the field enable/disable evaluator, unbound;
62
+ `three-slicer/toggle` is the same bound to upstream's rules.
63
+ - The `/slice` page opens and drops a `.gcode` file, parsed by the permissive parser.
64
+ - Third-party notices for both packages (`THIRD-PARTY-NOTICES.md`).
65
+
66
+ ### Changed
67
+
68
+ - The toolpath renderer is a rewrite from `TOOLPATH_SPEC.md`, not a port. Two behaviours changed with it:
69
+ `rangeColorAt` no longer extrapolates below the range's low end, and the `ColorContext` type matches what
70
+ the renderer actually reads (a `NaN` in the context is coerced instead of propagating).
71
+ - `<SettingsPanel/>` takes `schema`, `uiTree` and `toggle` as props. Without them it labels every field by its
72
+ key and disables nothing — the upstream labels and tooltips are AGPL prose and stay in `three-slicer/data`.
73
+ - The two packages publish as a **locked pair**: same version, and `three-slicer` pins `three-slicer-viewer`
74
+ exactly (`test_version_lockstep.mjs`). A release goes through the root `Makefile` (`make publish`), which
75
+ publishes the viewer first — the pin means `three-slicer@0.3.0` cannot be installed before it exists.
76
+ - `viewer-package/` is mirrored to `github.com/kimgh06/three-slicer-viewer` (a `git subtree split`); the monorepo
77
+ stays the source of truth.
78
+
79
+ ### Fixed
80
+
81
+ - A `three-slicer/settings` import that survived the split inside the slicing hook, which only tree-shaking
82
+ kept out of the MIT bundle. The license boundary test now scans raw specifiers in `.js/.jsx/.mjs/.d.ts/.css/
83
+ .json` (it used to strip string literals first — and an import path is one) and the built `dist/` too.
84
+
85
+ ## 0.2.5 — 2026-09-06
86
+
87
+ The per-plate release. A plate is a printer now, not just a position.
88
+
89
+ ### Added
90
+
91
+ - Per-plate settings: `plateSettings` (`{[plateIndex]: sparse map}`) beside `settings` — one plate, one printer,
92
+ one bed, one technology. A plate override is additive over the global map; with no override the global map
93
+ is used by identity, which is what keeps the pre-feature output byte-identical. A bed override resizes that
94
+ plate's grid cell (`plateLayoutHetero`), and a `printer_technology` override routes that plate to SLA beside
95
+ FFF neighbours. What a 3mf cannot represent is refused typed at export: `UNSUPPORTED_MIXED_TECH_3MF`,
96
+ `UNSUPPORTED_MIXED_BED_3MF`.
97
+ - `plateContext(settings, plateSettings, plate, dims)`: the one derivation of what a plate prints with —
98
+ effective map, technology, derived params, enforced frame. Everything that used to read the global `kp`
99
+ reads this.
100
+ - Slice every plate through a worker pool the viewer orchestrates (`slice_pool.js`): the selected plate first
101
+ on the selector worker, the rest on K pool workers; a pool worker that dies re-queues its plate to run alone
102
+ after the pool drains. K is capped by the largest plate's STL size — measured, not designed (README tables).
103
+ - Every resin plate keeps a preview in the scene, not only the focused one.
104
+ - SL1 masks are rasterized in-house (`raster_mask.js`, gray8 PNG) with an opt-in GPU anti-aliasing path
105
+ (`sla_antialias: 1|2|4`) and a slice-by-rendering parity path when the merged STL is still at hand.
106
+ - The SLA kernel reports progress from inside its contour phase, so an SLA plate no longer looks idle beside
107
+ FFF plates.
108
+ - The settings panel marks a plate's overridden keys.
109
+
110
+ ### Fixed
111
+
112
+ - A plate-scope profile pick is written whole and describes its own machine — a diff left keys shared with the
113
+ global printer off the override, and the next global printer change moved them.
114
+ - A model load drops only the plate it lands on, not every plate's cached result.
115
+
116
+ ## 0.2.4 — 2026-09-03
117
+
118
+ The painting release: the brush behaves like upstream's.
119
+
120
+ ### Added
121
+
122
+ - The brush draws itself (a ring for the circle cursor, a translucent ball for the sphere), and a stroke is a
123
+ capsule between samples rather than a row of spheres — a fast drag no longer leaves gaps.
124
+ - The brush's own tools and modifiers: fill, the section plane, the overhang limit, `Ctrl`+wheel for the
125
+ radius, `Alt`+wheel for the plane, `Shift`+drag to erase — upstream's bindings. The bare wheel is the camera
126
+ zoom again.
127
+ - Four kernel-side brush behaviours the bridge never exposed (`selector_paint_stroke`, the fill preview, the
128
+ clip plane, the overhang gate), each feature-detected so an older kernel keeps working.
129
+
130
+ ### Fixed
131
+
132
+ - The paint overlay no longer draws through the model.
133
+ - The filament colour and the filament selection are one thing: `extruderColors` and the `filament_colour`
134
+ settings key are written together (`actions/filament_colors.js`), so a project saved after recolouring comes
135
+ back in the new colours.
136
+
3
137
  ## 0.2.3 — 2026-08-29
4
138
 
5
139
  A developer-experience release: everything here comes from a log of what integrating the package actually
package/README.md CHANGED
@@ -4,12 +4,17 @@ 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)
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/Three_Slicer/main/web/viewer/public/usage.png)
8
8
 
9
9
  ## Links
10
10
 
11
+ - Permissive viewer: [`three-slicer-viewer`](https://www.npmjs.com/package/three-slicer-viewer) (MIT) — the
12
+ viewer, the settings form, G-code parsing and toolpath rendering without the slicer. This package plugs the
13
+ kernel and OrcaSlicer's vendor presets into it; `three-slicer/viewer` and `three-slicer/components` are that
14
+ composition. Which code carries which licence, and why: [`PROVENANCE.md`](PROVENANCE.md).
15
+
11
16
  - Package: [npmjs.com/package/three-slicer](https://www.npmjs.com/package/three-slicer)
12
- - Source: [kimgh06/Web_Three_Slicer](https://github.com/kimgh06/Web_Three_Slicer)
17
+ - Source: [kimgh06/Three_Slicer](https://github.com/kimgh06/Three_Slicer)
13
18
  - Demo: [slicer.kimgh06.com](https://slicer.kimgh06.com/)
14
19
 
15
20
  ## Start Here
@@ -20,7 +25,7 @@ reading whole; everything else is lookup.
20
25
  | Your case | Path | Copy this | Then read |
21
26
  | --- | --- | --- | --- |
22
27
  | "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) |
28
+ | A slicer inside your existing page | **Embed** | [Quick Start: React Viewer](#quick-start-react-viewer) | [viewer/README](../viewer-package/VIEWER.md) (330 lines — the props contracts live there) |
24
29
  | The full slicer UI — viewport + settings panel | **Full UI** | [Full React Example](#full-react-example) | viewer/README + [components/README](components/README.md) |
25
30
 
26
31
  Whichever path: read [Before You Ship](#before-you-ship) before deploying — those traps return plausible
@@ -167,7 +172,7 @@ export default function App() {
167
172
  `position: relative` and a real height. In a plain static `<div>` it escapes to the page instead. There is no width
168
173
  or height prop.
169
174
 
170
- 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.
175
+ 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-package/VIEWER.md](../viewer-package/VIEWER.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.
171
176
 
172
177
  ## Quick Start: Settings Panel
173
178
 
@@ -272,7 +277,7 @@ Some of the connections are not imports, and knowing which is which explains mos
272
277
  | --- | --- | --- |
273
278
  | `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
279
  | `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 |
280
+ | `types` → `engine` | the generator **executes** `deriveKernelParams` (probing) and writes `engine/PARAMS.md` + `../viewer-package/src/settings/kernel_setting_keys.js` back into the engine | `test_kernel_params.mjs` is the staleness gate |
276
281
  | `types` → `data` | `gen_settings_types.mjs` generates `settings-keys.d.ts` (976 keys) from the schema | run automatically by the build |
277
282
  | `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
283
  | inside `viewer` | `core` (pure, node-runnable) ← `scene` (three/DOM) ← `actions` ← `ui` | `test_layers.mjs` enforces the boundary |
@@ -387,7 +392,7 @@ options as arrays of strings — the same shape [3MF Projects](#3mf-projects) us
387
392
  through the same serializer.
388
393
 
389
394
  The viewer adds the zip forms and a UI: the printer card's **Load** and **Save** read and write these files, and a
390
- save routes through [`onExport`](viewer/README.md#taking-the-saves) like every other download.
395
+ save routes through [`onExport`](../viewer-package/VIEWER.md#taking-the-saves) like every other download.
391
396
 
392
397
  ## Settings UI: Enable/Disable Rules
393
398
 
@@ -782,7 +787,7 @@ the layout by hand.
782
787
 
783
788
  ## Before You Ship
784
789
 
785
- The traps below were all hit while building the [integration demos](https://github.com/kimgh06/Web_Three_Slicer/tree/main/examples)
790
+ The traps below were all hit while building the [integration demos](https://github.com/kimgh06/Three_Slicer/tree/main/examples)
786
791
  and the live landing embed — each one returns something plausible instead of an error, which is why they
787
792
  are listed **in the order they bite**, not by topic. The later a trap fires, the more it costs — and none of
788
793
  them fails a build or throws, so nothing surfaces them for you.
@@ -832,4 +837,4 @@ production while `npm run dev` passed. The no-argument form now emits a real wor
832
837
 
833
838
  ## License
834
839
 
835
- AGPL-3.0-or-later. This package is derived from OrcaSlicer. Source is available at [kimgh06/Web_Three_Slicer](https://github.com/kimgh06/Web_Three_Slicer). If you embed it in a web app, make sure your app complies with the AGPL network-use requirements, including offering source code to users where required.
840
+ AGPL-3.0-or-later. This package is derived from OrcaSlicer. Source is available at [kimgh06/Three_Slicer](https://github.com/kimgh06/Three_Slicer). If you embed it in a web app, make sure your app complies with the AGPL network-use requirements, including offering source code to users where required.
@@ -1,7 +1,7 @@
1
1
  # Third-party notices
2
2
 
3
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 —
4
+ kernels embedded in `engine/src/slicer_core.js` / `slicer_core.mt.js` —
5
5
  contain compiled or bundled code from the following projects. Full license texts ship in the source
6
6
  repository at the paths given (the package's `repository` field points there); the kernel's vendored copies
7
7
  live under `packages/wasm-core/third_party/deps_src/`.
@@ -23,7 +23,12 @@ live under `packages/wasm-core/third_party/deps_src/`.
23
23
  | SGI glu-libtess (the GLU tessellator) | SGI Free Software License B 2.0 | `deps_src/glu-libtess/` (header of each source file) |
24
24
  | Emscripten runtime (the generated JS glue around the kernels) | MIT | emscripten.org |
25
25
 
26
- ## Bundled into the viewer/components builds
26
+ ## The viewer and the settings panel
27
+
28
+ Since the split, `three-slicer/viewer` and `three-slicer/components` are re-exports of `three-slicer-viewer` (MIT), a
29
+ separate package with its own `THIRD-PARTY-NOTICES.md`. This package bundles no viewer code of its own.
30
+
31
+ ## Bundled into the viewer/components builds (historical — see above)
27
32
 
28
33
  | Component | License | Note |
29
34
  | --- | --- | --- |
@@ -1,201 +1,15 @@
1
- import t, { useRef as T, useState as h, useLayoutEffect as B, useEffect as I, useMemo as D } from "react";
2
- import { createPortal as R } from "react-dom";
3
- import { uiTree as j, schema as A } from "three-slicer/data";
4
- import { printerTechnology as $, settingRaw as U } from "three-slicer/settings";
5
- import { disabledKeys as W, makeCfg as q } from "three-slicer/toggle";
6
- function G({ css: e, className: s, children: a }) {
7
- const l = T(null), [i] = h(() => {
8
- const r = document.createElement("div");
9
- return r.style.display = "contents", r;
10
- });
11
- return B(() => {
12
- const r = l.current, d = r.shadowRoot || r.attachShadow({ mode: "open" });
13
- let c = d.querySelector("style[data-ts-css]");
14
- c || (c = document.createElement("style"), c.setAttribute("data-ts-css", ""), d.append(c)), c.textContent = e, d.contains(i) || d.append(i);
15
- }, [e, i]), /* @__PURE__ */ t.createElement("div", { ref: l, className: s }, R(a, i));
16
- }
17
- const H = ':host{display:contents}*{box-sizing:border-box}.row{display:grid;grid-template-columns:260px 220px 70px 1fr;gap:8px;align-items:center;padding:3px 0;border-bottom:1px dashed #f0f0f0}.row .lbl{color:#222;text-decoration:none}.row .lbl:hover{color:#0a7d4f;text-decoration:underline}.row .key{font-size:11px;color:#999;overflow:hidden;text-overflow:ellipsis}.row input[type=text],.row input:not([type]),.row select,.row textarea{width:100%;max-width:260px;padding:2px 6px}.inputwrap{display:inline-flex;align-items:center;gap:4px}.unit{color:#888;font-size:12px;white-space:nowrap}.pts{font-size:11px;color:#666}.badge{font-size:10px;padding:1px 6px;border-radius:8px;color:#fff;text-align:center}.mode-simple{background:#2e9e5b}.mode-advanced{background:#3673c3}.mode-expert{background:#d9822b}.mode-develop{background:#c0392b}.mode-none,.mode-undefined{background:#aaa}.muted{color:#999;font-weight:400;font-size:12px}.row.custom{grid-template-columns:1fr}.row.disabled{opacity:.42}.row.disabled input,.row.disabled select,.row.disabled textarea{cursor:not-allowed;background:#f2f2f2}.lbl-cell{display:inline-flex;align-items:center;gap:5px;min-width:0}.lbl-cell .lbl{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dirty-dot{flex:0 0 auto;width:7px;height:7px;border-radius:50%;background:#e8873a;box-shadow:0 0 0 2px #fff3e6}.override-dot{flex:0 0 auto;width:9px;height:9px;border-radius:50%;background:#f0a542;border:0;padding:0;cursor:pointer;box-shadow:0 0 0 2px #fdf1dd}.override-dot:hover{background:#d98c26}.widget-cell{display:inline-flex;align-items:center;gap:6px;min-width:0}.reset-btn{flex:0 0 auto;border:1px solid #d8b48a;background:#fbf1e4;color:#b5701f;border-radius:5px;cursor:pointer;font-size:13px;line-height:1;padding:1px 5px}.reset-btn:hover{background:#f3e2cb}.settings-panel{flex:0 0 400px;width:400px;border-left:1px solid #e2e2e2;display:flex;flex-direction:column;min-height:0;background:#fff;color:#222;font:14px/1.5 -apple-system,Segoe UI,sans-serif}.settings-panel .sp-top{padding:10px 12px 8px;border-bottom:1px solid #eee}.sp-builder{display:flex;align-items:center;gap:8px;font-size:12px;color:#666}.sp-builder select{flex:1;padding:4px 6px}.sp-pages{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}.sp-pages button{border:1px solid #e0e0e0;background:#fafafa;border-radius:6px;padding:4px 8px;font-size:12px;cursor:pointer}.sp-pages button.on{background:#e6f5ee;color:#0a7d4f;border-color:#b7e2cd;font-weight:700}.sp-modes{display:flex;gap:4px;margin-top:8px}.sp-modes button{border:0;background:none;font-size:11px;color:#888;padding:2px 6px;border-radius:6px;cursor:pointer}.sp-modes button.on{background:#eef2f0;color:#0a7d4f;font-weight:700}.settings-panel .sp-body{flex:1 1 auto;overflow-y:auto;padding:8px 14px 24px}.settings-panel .sp-body h2{font-size:16px}.settings-panel .row{grid-template-columns:1fr auto;grid-template-areas:"lbl badge" "key widget";row-gap:2px;padding:6px 0}.settings-panel .row .lbl{grid-area:lbl;font-weight:600}.settings-panel .row .badge{grid-area:badge;justify-self:end}.settings-panel .row .key{grid-area:key}.settings-panel .row .inputwrap,.settings-panel .row>select,.settings-panel .row>input,.settings-panel .row>textarea,.settings-panel .row>.pts,.settings-panel .row>.muted{grid-area:widget;justify-self:end}.settings-panel .row input:not([type=checkbox]):not([type=color]),.settings-panel .row select,.settings-panel .row textarea{max-width:200px}.settings-panel .row.custom{grid-template-columns:1fr;grid-template-areas:"lbl"}.sp-search{width:100%;box-sizing:border-box;padding:6px 9px;border:1px solid #d6d6d6;border-radius:6px;font-size:13px;margin-bottom:8px}:host(.sp-embedded) .settings-panel{position:static;width:auto;flex:none;border:0;height:auto}:host(.sp-embedded) .sp-body{padding:4px 2px 8px;max-height:460px}.bse{display:flex;flex-direction:column;gap:6px;min-width:0;max-width:220px}.bse-modes{display:flex;gap:4px}.bse-modes button{border:1px solid #e0e0e0;background:#fafafa;border-radius:6px;padding:2px 8px;font-size:11px;cursor:pointer}.bse-modes button.on{background:#e6f5ee;color:#0a7d4f;border-color:#b7e2cd;font-weight:700}.bse-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px 8px}.bse-grid label{display:flex;align-items:center;gap:4px;font-size:11px;color:#666;white-space:nowrap}.bse-grid input{width:56px;padding:2px 4px;font-size:12px}.bse-json{width:100%;font:11px ui-monospace,monospace;padding:4px 6px;border:1px solid #d6d6d6;border-radius:6px;box-sizing:border-box}.bse-json.err{border-color:#d0435a;background:#fdf3f4}', L = (e, s, a, l) => [[a, l], [e + a, l], [e + a, s + l], [a, s + l]], Q = (e) => {
18
- const s = e / 2, a = 32;
19
- return Array.from({ length: a }, (l, i) => {
20
- const r = 2 * Math.PI * i / a;
21
- return [+(s + s * Math.cos(r)).toFixed(2), +(s + s * Math.sin(r)).toFixed(2)];
22
- });
23
- }, P = (e) => {
24
- const s = e.map((l) => l[0]), a = e.map((l) => l[1]);
25
- return { x0: Math.min(...s), y0: Math.min(...a), w: Math.max(...s) - Math.min(...s), d: Math.max(...a) - Math.min(...a) };
26
- };
27
- function X({ value: e, onChange: s, disabled: a }) {
28
- const l = Array.isArray(e) && e.length >= 3 ? e : L(200, 200, 0, 0), i = P(l), [r, d] = h(l.length === 4 ? "rect" : l.length >= 12 ? "circle" : "custom"), [c, u] = h({ w: i.w, d: i.d, ox: i.x0, oy: i.y0 }), [N, g] = h(Math.round(i.w)), [p, b] = h(JSON.stringify(l)), [w, E] = h(!1);
29
- I(() => {
30
- const n = P(l);
31
- u({ w: n.w, d: n.d, ox: n.x0, oy: n.y0 }), g(Math.round(n.w)), b(JSON.stringify(l)), E(!1);
32
- }, [JSON.stringify(e)]);
33
- const v = (n) => {
34
- u(n);
35
- const { w: m, d: x, ox: _, oy: y } = n;
36
- m > 0 && x > 0 && s(L(m, x, _, y));
37
- }, z = (n) => {
38
- g(n), n > 0 && s(Q(n));
39
- }, C = (n) => {
40
- b(n);
41
- try {
42
- const m = JSON.parse(n);
43
- if (!Array.isArray(m) || m.length < 3 || !m.every((x) => Array.isArray(x) && x.length === 2 && x.every(Number.isFinite))) throw 0;
44
- E(!1), s(m);
45
- } catch {
46
- E(!0);
47
- }
48
- }, k = (n) => {
49
- const m = parseFloat(n);
50
- return Number.isFinite(m) ? m : 0;
51
- };
52
- return /* @__PURE__ */ t.createElement("div", { className: "bse", "data-testid": "bed-shape-editor" }, /* @__PURE__ */ t.createElement("div", { className: "bse-modes" }, [["rect", "Rectangular"], ["circle", "Circular"], ["custom", "Custom"]].map(([n, m]) => /* @__PURE__ */ t.createElement("button", { key: n, type: "button", className: r === n ? "on" : "", disabled: a, onClick: () => d(n) }, m))), r === "rect" && /* @__PURE__ */ t.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ t.createElement("label", null, "Width ", /* @__PURE__ */ t.createElement(
53
- "input",
54
- {
55
- type: "number",
56
- min: "1",
57
- value: c.w,
58
- disabled: a,
59
- "data-testid": "bed-w",
60
- onChange: (n) => v({ ...c, w: k(n.target.value) })
61
- }
62
- ), " mm"), /* @__PURE__ */ t.createElement("label", null, "Depth ", /* @__PURE__ */ t.createElement(
63
- "input",
64
- {
65
- type: "number",
66
- min: "1",
67
- value: c.d,
68
- disabled: a,
69
- "data-testid": "bed-d",
70
- onChange: (n) => v({ ...c, d: k(n.target.value) })
71
- }
72
- ), " mm"), /* @__PURE__ */ t.createElement("label", null, "Origin X ", /* @__PURE__ */ t.createElement(
73
- "input",
74
- {
75
- type: "number",
76
- value: c.ox,
77
- disabled: a,
78
- onChange: (n) => v({ ...c, ox: k(n.target.value) })
79
- }
80
- )), /* @__PURE__ */ t.createElement("label", null, "Origin Y ", /* @__PURE__ */ t.createElement(
81
- "input",
82
- {
83
- type: "number",
84
- value: c.oy,
85
- disabled: a,
86
- onChange: (n) => v({ ...c, oy: k(n.target.value) })
87
- }
88
- ))), r === "circle" && /* @__PURE__ */ t.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ t.createElement("label", null, "Diameter ", /* @__PURE__ */ t.createElement(
89
- "input",
90
- {
91
- type: "number",
92
- min: "1",
93
- value: N,
94
- disabled: a,
95
- "data-testid": "bed-dia",
96
- onChange: (n) => z(k(n.target.value))
97
- }
98
- ), " mm")), r === "custom" && /* @__PURE__ */ t.createElement(
99
- "textarea",
100
- {
101
- className: "bse-json" + (w ? " err" : ""),
102
- rows: 3,
103
- value: p,
104
- disabled: a,
105
- "data-testid": "bed-json",
106
- onChange: (n) => C(n.target.value),
107
- placeholder: "[[0,0],[200,0],[200,200],[0,200]]"
108
- }
109
- ));
110
- }
111
- const Y = {
112
- printable_area: X
113
- }, J = Object.keys(j).filter((e) => j[e].length > 0), V = ["all", "simple", "advanced", "expert", "develop"], Z = (e) => e.replace("::build", "").replace("Tab", "").replace("_sla", " (SLA)"), K = (e) => /SLA|build_sla\b/.test(e) ? "SLA" : "FFF";
114
- function ee(e, s) {
115
- var i;
116
- if (!e || !(s in e)) return !1;
117
- const a = (i = A[s]) == null ? void 0 : i.default, l = Array.isArray(a) ? a[0] : a;
118
- return String(e[s]) !== String(l ?? "");
119
- }
120
- function te(e, s) {
121
- var l;
122
- if (s === "all" || e.startsWith("<")) return !0;
123
- const a = (l = A[e]) == null ? void 0 : l.mode;
124
- return s === "simple" ? a === "simple" : s === "advanced" ? a === "simple" || a === "advanced" : !0;
125
- }
126
- function ae(e) {
127
- return e ? e.gui_type === "color" ? "color" : e.enum_values ? "select" : e.type === "coBool" || e.type === "coBools" ? "checkbox" : e.multiline || e.is_code ? "textarea" : e.type === "coPoints" || e.type === "coPoint" ? "points" : "input" : "unknown";
128
- }
129
- function re({ mode: e }) {
130
- return /* @__PURE__ */ t.createElement("span", { className: `badge mode-${e ?? "none"}` }, e ?? "—");
131
- }
132
- function ne({ def: e, optKey: s, settings: a, setSettings: l, disabled: i, customWidgets: r }) {
133
- const d = U(a, s), c = (p) => l((b) => ({ ...b, [s]: p })), u = (r == null ? void 0 : r[s]) ?? Y[s];
134
- if (u) return /* @__PURE__ */ t.createElement(u, { def: e, value: d, onChange: c, disabled: i });
135
- const N = ae(e), g = Array.isArray(d) ? d[0] : d;
136
- switch (N) {
137
- case "select":
138
- return /* @__PURE__ */ t.createElement("select", { value: g ?? "", onChange: (p) => c(p.target.value), disabled: i }, e.enum_values.map((p, b) => {
139
- var w;
140
- return /* @__PURE__ */ t.createElement("option", { key: p, value: p }, ((w = e.enum_labels) == null ? void 0 : w[b]) ?? p);
141
- }));
142
- case "checkbox":
143
- return /* @__PURE__ */ t.createElement("input", { type: "checkbox", checked: Array.isArray(d) ? !!d[0] : !!d, onChange: (p) => c(p.target.checked), disabled: i });
144
- case "color":
145
- return /* @__PURE__ */ t.createElement("input", { type: "color", value: typeof g == "string" && g.startsWith("#") ? g : "#00ae42", onChange: (p) => c(p.target.value), disabled: i });
146
- case "textarea":
147
- return /* @__PURE__ */ t.createElement("textarea", { rows: 2, value: Array.isArray(d) ? d.join(`
148
- `) : d ?? "", onChange: (p) => c(p.target.value), disabled: i });
149
- case "points":
150
- return /* @__PURE__ */ t.createElement("code", { className: "pts" }, JSON.stringify(d ?? []));
151
- case "unknown":
152
- return /* @__PURE__ */ t.createElement("span", { className: "muted" }, "No schema");
153
- default:
154
- return /* @__PURE__ */ t.createElement("span", { className: "inputwrap" }, /* @__PURE__ */ t.createElement("input", { value: g ?? "", onChange: (p) => c(p.target.value), disabled: i }), e.sidetext && /* @__PURE__ */ t.createElement("span", { className: "unit" }, e.sidetext));
155
- }
156
- }
157
- function se({ optKey: e, settings: s, setSettings: a, disabled: l, onOptionOpen: i, customWidgets: r, overridden: d, onRevertKey: c }) {
158
- if (e.startsWith("<")) return /* @__PURE__ */ t.createElement("div", { className: "row custom" }, /* @__PURE__ */ t.createElement("span", { className: "muted" }, "⚙ Custom widget ", e));
159
- const u = A[e], N = l ? l[e] : void 0, g = N != null, p = ee(s, e), b = () => a((E) => {
160
- const v = { ...E };
161
- return delete v[e], v;
162
- }), w = (u == null ? void 0 : u.label) || (u == null ? void 0 : u.full_label) || e;
163
- return /* @__PURE__ */ t.createElement("div", { className: "row" + (g ? " disabled" : ""), title: g ? `Disabled when: ${N}` : (u == null ? void 0 : u.tooltip) ?? "", "data-testid": `row-${e}` }, /* @__PURE__ */ t.createElement("span", { className: "lbl-cell" }, d && /* @__PURE__ */ t.createElement(
164
- "button",
165
- {
166
- type: "button",
167
- className: "override-dot",
168
- onClick: () => c == null ? void 0 : c(e),
169
- title: "This plate's override — click to follow the global value again",
170
- "data-testid": `override-${e}`
171
- }
172
- ), p && /* @__PURE__ */ t.createElement("span", { className: "dirty-dot", title: "Changed from default", "data-testid": `dirty-${e}` }), i ? /* @__PURE__ */ t.createElement("button", { type: "button", className: "lbl lbl-link", onClick: () => i(e), title: "Details" }, w) : /* @__PURE__ */ t.createElement("span", { className: "lbl" }, w)), /* @__PURE__ */ t.createElement("code", { className: "key" }, e), /* @__PURE__ */ t.createElement(re, { mode: u == null ? void 0 : u.mode }), /* @__PURE__ */ t.createElement("span", { className: "widget-cell" }, /* @__PURE__ */ t.createElement(ne, { def: u, optKey: e, settings: s, setSettings: a, disabled: g, customWidgets: r }), p && /* @__PURE__ */ t.createElement("button", { className: "reset-btn", onClick: b, title: "Reset to default", "data-testid": `reset-${e}` }, "↺")));
173
- }
174
- function pe({ settings: e, setSettings: s, onOptionOpen: a, embedded: l = !1, customWidgets: i, only: r, overriddenKeys: d, onRevertKey: c }) {
175
- const [u, N] = h((r == null ? void 0 : r.builder) ?? J[0] ?? ""), [g, p] = h(0), [b, w] = h("all"), [E, v] = h(""), z = $(e), C = J.filter((o) => K(o) === z), k = (r == null ? void 0 : r.builder) ?? (C.includes(u) ? u : C[0] ?? ""), n = j[(r == null ? void 0 : r.builder) ?? k] ?? [], m = r != null && r.page ? n.filter((o) => o.page === r.page) : n, x = m[Math.min(g, m.length - 1)], _ = D(() => W(q(e)), [e]), y = E.trim().toLowerCase(), S = D(() => y ? Object.entries(A).filter(([o, f]) => {
176
- var M, O;
177
- return o.includes(y) || ((M = f.label) == null ? void 0 : M.toLowerCase().includes(y)) || ((O = f.full_label) == null ? void 0 : O.toLowerCase().includes(y));
178
- }).slice(0, 60).map(([o]) => o) : [], [y]), F = (o, f) => /* @__PURE__ */ t.createElement(
179
- se,
180
- {
181
- key: o + f,
182
- optKey: o,
183
- settings: e,
184
- setSettings: s,
185
- disabled: _,
186
- onOptionOpen: a,
187
- customWidgets: i,
188
- overridden: d == null ? void 0 : d.includes(o),
189
- onRevertKey: c
190
- }
191
- );
192
- return /* @__PURE__ */ t.createElement(G, { css: H, className: l ? "sp-embedded" : void 0 }, /* @__PURE__ */ t.createElement("div", { className: "settings-panel" }, !r && /* @__PURE__ */ t.createElement("div", { className: "sp-top" }, /* @__PURE__ */ t.createElement("input", { className: "sp-search", placeholder: `Search options (${Object.keys(A).length})…`, value: E, onChange: (o) => v(o.target.value), "data-testid": "opt-search" }), !y && /* @__PURE__ */ t.createElement(t.Fragment, null, /* @__PURE__ */ t.createElement("label", { className: "sp-builder" }, /* @__PURE__ */ t.createElement("span", null, "Settings group"), /* @__PURE__ */ t.createElement("select", { value: k, onChange: (o) => {
193
- N(o.target.value), p(0);
194
- } }, C.map((o) => /* @__PURE__ */ t.createElement("option", { key: o, value: o }, Z(o))))), /* @__PURE__ */ t.createElement("div", { className: "sp-pages" }, m.map((o, f) => /* @__PURE__ */ t.createElement("button", { key: o.page + f, className: f === Math.min(g, m.length - 1) ? "on" : "", onClick: () => p(f) }, o.page))), /* @__PURE__ */ t.createElement("div", { className: "sp-modes" }, V.map((o) => /* @__PURE__ */ t.createElement("button", { key: o, className: b === o ? "on" : "", onClick: () => w(o) }, o))))), /* @__PURE__ */ t.createElement("div", { className: "sp-body" }, y ? /* @__PURE__ */ t.createElement("section", { "data-testid": "search-results" }, /* @__PURE__ */ t.createElement("h3", null, "Search: “", E, "” — ", S.length, " hit", S.length === 1 ? "" : "s", S.length === 60 ? "+" : ""), S.map(F), !S.length && /* @__PURE__ */ t.createElement("p", { className: "muted" }, "No matches")) : x ? /* @__PURE__ */ t.createElement(t.Fragment, null, !r && /* @__PURE__ */ t.createElement("h2", null, x.page, " ", /* @__PURE__ */ t.createElement("span", { className: "muted" }, "Tab.cpp:", x.line)), x.groups.map((o) => {
195
- const f = o.options.filter((M) => te(M, b) && !(r && M.startsWith("<")));
196
- return f.length ? /* @__PURE__ */ t.createElement("section", { key: o.group + o.line }, /* @__PURE__ */ t.createElement("h3", null, o.group || "(untitled group)", " ", /* @__PURE__ */ t.createElement("span", { className: "muted" }, ":", o.line)), f.map(F)) : null;
197
- })) : /* @__PURE__ */ t.createElement("p", { className: "muted" }, "No builder"))));
1
+ import r from "react";
2
+ import t from "three-slicer-viewer/components";
3
+ import { uiTree as o, schema as m } from "three-slicer/data";
4
+ import { schema as d, uiTree as h } from "three-slicer/data";
5
+ import { disabledKeys as a, makeCfg as i } from "three-slicer/toggle";
6
+ const f = { makeCfg: i, disabledKeys: a };
7
+ function l(e) {
8
+ return /* @__PURE__ */ r.createElement(t, { schema: m, uiTree: o, toggle: f, ...e });
198
9
  }
199
10
  export {
200
- pe as default
11
+ l as default,
12
+ d as schema,
13
+ f as toggle,
14
+ h as uiTree
201
15
  };