three-slicer 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +9 -4
  3. package/THIRD-PARTY-NOTICES.md +7 -2
  4. package/components/dist/SettingsPanel.js +12 -176
  5. package/data/config-schema-lean.json +1 -0
  6. package/data/ui-tree-keys.json +1 -0
  7. package/engine/src/client.js +12 -2
  8. package/engine/src/data.js +6 -0
  9. package/engine/src/settings.js +12 -627
  10. package/engine/src/slicer.worker.js +6 -1
  11. package/engine/src/slicer_core.js +0 -0
  12. package/engine/src/slicer_core.mt.js +0 -0
  13. package/engine/src/toggle_eval.js +9 -77
  14. package/package.json +20 -10
  15. package/types/client.d.ts +5 -2
  16. package/types/components.d.ts +3 -29
  17. package/types/settings.d.ts +3 -0
  18. package/types/toggle.d.ts +2 -22
  19. package/types/viewer-gcode.d.ts +2 -29
  20. package/types/viewer-loaders.d.ts +1 -33
  21. package/types/viewer-toolpath.d.ts +3 -113
  22. package/types/viewer.d.ts +6 -174
  23. package/types/worker.d.ts +2 -1
  24. package/viewer/dist/Viewport.js +9 -5489
  25. package/viewer/dist/gcode_reexport.js +1 -0
  26. package/viewer/dist/loaders_reexport.js +1 -0
  27. package/viewer/dist/toolpath_reexport.js +1 -0
  28. package/components/README.md +0 -44
  29. package/components/styles.css +0 -84
  30. package/engine/src/kernel_setting_keys.js +0 -132
  31. package/engine/src/preset_file.js +0 -99
  32. package/viewer/README.md +0 -375
  33. package/viewer/dist/gcode_parse.js +0 -190
  34. package/viewer/dist/make_worker.js +0 -31
  35. package/viewer/dist/model_loaders.js +0 -184
  36. package/viewer/dist/parse_3mf-C2X_W442.js +0 -300
  37. package/viewer/dist/parse_3mf.worker.js +0 -10
  38. package/viewer/dist/sl1_encode.worker.js +0 -10
  39. package/viewer/dist/sl1_write-UHmQ_itC.js +0 -170
  40. package/viewer/dist/sla_reconstruct-CXkYhux2.js +0 -176
  41. package/viewer/dist/sla_reconstruct.worker.js +0 -24
  42. package/viewer/dist/sla_slice.worker.js +0 -74
  43. package/viewer/dist/toolpath_gpu-4foc4FaU.js +0 -482
  44. package/viewer/dist/toolpath_gpu.js +0 -13
  45. package/viewer/styles.css +0 -353
package/CHANGELOG.md CHANGED
@@ -1,5 +1,99 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 — 2026-09-08
4
+
5
+ The viewer is its own package. Everything that displays — model loading, G-code parsing, the GPU toolpath
6
+ renderer, the settings transforms, `<Viewport/>` and `<SettingsPanel/>` — now ships as
7
+ [`three-slicer-viewer`](https://www.npmjs.com/package/three-slicer-viewer) under **MIT**, so it can go into
8
+ closed-source products. `three-slicer` stays AGPL and keeps every entry point it had: `three-slicer/viewer` and
9
+ `three-slicer/components` are now thin wrappers that plug the WASM kernel and the vendor catalog into the
10
+ permissive package. Which files could carry which license, and why, is `packages/PROVENANCE.md`; the plan the
11
+ split followed is `packages/RELICENSE.md`.
12
+
13
+ ### Added
14
+
15
+ - `three-slicer-viewer` (MIT): `<Viewport/>` with `slicer` and `catalog` props (defaults: a no-op slicer and an
16
+ empty catalog), `useSlicer({ makeWorker })` — the slicing hook with the kernel injected —, `/toolpath`,
17
+ `/gcode`, `/loaders`, `/settings`, `/data`, `/toggle`, `/components`, and the `.d.ts` for each.
18
+ - `makeSlicerWorker` in `three-slicer/client` and `bundledCatalog` in `three-slicer/settings`: the two things
19
+ that cannot be permissive, exported from where they live so a host can compose the viewer with the kernel
20
+ by hand (`web/viewer/src/Prepare.jsx` does).
21
+ - `three-slicer-viewer/toggle`: `makeToggle(rules, schema)` — the field enable/disable evaluator, unbound;
22
+ `three-slicer/toggle` is the same bound to upstream's rules.
23
+ - The `/slice` page opens and drops a `.gcode` file, parsed by the permissive parser.
24
+ - Third-party notices for both packages (`THIRD-PARTY-NOTICES.md`).
25
+
26
+ ### Changed
27
+
28
+ - The toolpath renderer is a rewrite from `TOOLPATH_SPEC.md`, not a port. Two behaviours changed with it:
29
+ `rangeColorAt` no longer extrapolates below the range's low end, and the `ColorContext` type matches what
30
+ the renderer actually reads (a `NaN` in the context is coerced instead of propagating).
31
+ - `<SettingsPanel/>` takes `schema`, `uiTree` and `toggle` as props. Without them it labels every field by its
32
+ key and disables nothing — the upstream labels and tooltips are AGPL prose and stay in `three-slicer/data`.
33
+ - The two packages publish as a **locked pair**: same version, and `three-slicer` pins `three-slicer-viewer`
34
+ exactly (`test_version_lockstep.mjs`). A release goes through the root `Makefile` (`make publish`), which
35
+ publishes the viewer first — the pin means `three-slicer@0.3.0` cannot be installed before it exists.
36
+ - `packages-mit/` is mirrored to `github.com/kimgh06/three-slicer-viewer` (a `git subtree split`); the monorepo
37
+ stays the source of truth.
38
+
39
+ ### Fixed
40
+
41
+ - A `three-slicer/settings` import that survived the split inside the slicing hook, which only tree-shaking
42
+ kept out of the MIT bundle. The license boundary test now scans raw specifiers in `.js/.jsx/.mjs/.d.ts/.css/
43
+ .json` (it used to strip string literals first — and an import path is one) and the built `dist/` too.
44
+
45
+ ## 0.2.5 — 2026-09-06
46
+
47
+ The per-plate release. A plate is a printer now, not just a position.
48
+
49
+ ### Added
50
+
51
+ - Per-plate settings: `plateSettings` (`{[plateIndex]: sparse map}`) beside `settings` — one plate, one printer,
52
+ one bed, one technology. A plate override is additive over the global map; with no override the global map
53
+ is used by identity, which is what keeps the pre-feature output byte-identical. A bed override resizes that
54
+ plate's grid cell (`plateLayoutHetero`), and a `printer_technology` override routes that plate to SLA beside
55
+ FFF neighbours. What a 3mf cannot represent is refused typed at export: `UNSUPPORTED_MIXED_TECH_3MF`,
56
+ `UNSUPPORTED_MIXED_BED_3MF`.
57
+ - `plateContext(settings, plateSettings, plate, dims)`: the one derivation of what a plate prints with —
58
+ effective map, technology, derived params, enforced frame. Everything that used to read the global `kp`
59
+ reads this.
60
+ - Slice every plate through a worker pool the viewer orchestrates (`slice_pool.js`): the selected plate first
61
+ on the selector worker, the rest on K pool workers; a pool worker that dies re-queues its plate to run alone
62
+ after the pool drains. K is capped by the largest plate's STL size — measured, not designed (README tables).
63
+ - Every resin plate keeps a preview in the scene, not only the focused one.
64
+ - SL1 masks are rasterized in-house (`raster_mask.js`, gray8 PNG) with an opt-in GPU anti-aliasing path
65
+ (`sla_antialias: 1|2|4`) and a slice-by-rendering parity path when the merged STL is still at hand.
66
+ - The SLA kernel reports progress from inside its contour phase, so an SLA plate no longer looks idle beside
67
+ FFF plates.
68
+ - The settings panel marks a plate's overridden keys.
69
+
70
+ ### Fixed
71
+
72
+ - A plate-scope profile pick is written whole and describes its own machine — a diff left keys shared with the
73
+ global printer off the override, and the next global printer change moved them.
74
+ - A model load drops only the plate it lands on, not every plate's cached result.
75
+
76
+ ## 0.2.4 — 2026-09-03
77
+
78
+ The painting release: the brush behaves like upstream's.
79
+
80
+ ### Added
81
+
82
+ - The brush draws itself (a ring for the circle cursor, a translucent ball for the sphere), and a stroke is a
83
+ capsule between samples rather than a row of spheres — a fast drag no longer leaves gaps.
84
+ - The brush's own tools and modifiers: fill, the section plane, the overhang limit, `Ctrl`+wheel for the
85
+ radius, `Alt`+wheel for the plane, `Shift`+drag to erase — upstream's bindings. The bare wheel is the camera
86
+ zoom again.
87
+ - Four kernel-side brush behaviours the bridge never exposed (`selector_paint_stroke`, the fill preview, the
88
+ clip plane, the overhang gate), each feature-detected so an older kernel keeps working.
89
+
90
+ ### Fixed
91
+
92
+ - The paint overlay no longer draws through the model.
93
+ - The filament colour and the filament selection are one thing: `extruderColors` and the `filament_colour`
94
+ settings key are written together (`actions/filament_colors.js`), so a project saved after recolouring comes
95
+ back in the new colours.
96
+
3
97
  ## 0.2.3 — 2026-08-29
4
98
 
5
99
  A developer-experience release: everything here comes from a log of what integrating the package actually
package/README.md CHANGED
@@ -8,6 +8,11 @@ The package is published as a single npm package, `three-slicer`, with subpath e
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
17
  - Source: [kimgh06/Web_Three_Slicer](https://github.com/kimgh06/Web_Three_Slicer)
13
18
  - Demo: [slicer.kimgh06.com](https://slicer.kimgh06.com/)
@@ -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](../packages-mit/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 [../packages-mit/VIEWER.md](../packages-mit/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` + `../packages-mit/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`](../packages-mit/VIEWER.md#taking-the-saves) like every other download.
391
396
 
392
397
  ## Settings UI: Enable/Disable Rules
393
398
 
@@ -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,179 +1,15 @@
1
- import e, { useRef as L, useState as w, useLayoutEffect as P, useEffect as J, useMemo as F } from "react";
2
- import { createPortal as B } from "react-dom";
3
- import { uiTree as _, schema as S } from "three-slicer/data";
4
- import { printerTechnology as I, settingRaw as T } from "three-slicer/settings";
5
- import { disabledKeys as $, makeCfg as U } from "three-slicer/toggle";
6
- function W({ css: t, className: s, children: n }) {
7
- const i = L(null), [c] = w(() => {
8
- const r = document.createElement("div");
9
- return r.style.display = "contents", r;
10
- });
11
- return P(() => {
12
- const r = i.current, o = r.shadowRoot || r.attachShadow({ mode: "open" });
13
- let p = o.querySelector("style[data-ts-css]");
14
- p || (p = document.createElement("style"), p.setAttribute("data-ts-css", ""), o.append(p)), p.textContent = t, o.contains(c) || o.append(c);
15
- }, [t, c]), /* @__PURE__ */ e.createElement("div", { ref: i, className: s }, B(n, c));
16
- }
17
- const q = ':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}.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}', O = (t, s, n, i) => [[n, i], [t + n, i], [t + n, s + i], [n, s + i]], G = (t) => {
18
- const s = t / 2, n = 32;
19
- return Array.from({ length: n }, (i, c) => {
20
- const r = 2 * Math.PI * c / n;
21
- return [+(s + s * Math.cos(r)).toFixed(2), +(s + s * Math.sin(r)).toFixed(2)];
22
- });
23
- }, R = (t) => {
24
- const s = t.map((i) => i[0]), n = t.map((i) => i[1]);
25
- return { x0: Math.min(...s), y0: Math.min(...n), w: Math.max(...s) - Math.min(...s), d: Math.max(...n) - Math.min(...n) };
26
- };
27
- function H({ value: t, onChange: s, disabled: n }) {
28
- const i = Array.isArray(t) && t.length >= 3 ? t : O(200, 200, 0, 0), c = R(i), [r, o] = w(i.length === 4 ? "rect" : i.length >= 12 ? "circle" : "custom"), [p, b] = w({ w: c.w, d: c.d, ox: c.x0, oy: c.y0 }), [E, g] = w(Math.round(c.w)), [d, x] = w(JSON.stringify(i)), [y, k] = w(!1);
29
- J(() => {
30
- const a = R(i);
31
- b({ w: a.w, d: a.d, ox: a.x0, oy: a.y0 }), g(Math.round(a.w)), x(JSON.stringify(i)), k(!1);
32
- }, [JSON.stringify(t)]);
33
- const v = (a) => {
34
- b(a);
35
- const { w: u, d: m, ox: N, oy: z } = a;
36
- u > 0 && m > 0 && s(O(u, m, N, z));
37
- }, M = (a) => {
38
- g(a), a > 0 && s(G(a));
39
- }, A = (a) => {
40
- x(a);
41
- try {
42
- const u = JSON.parse(a);
43
- if (!Array.isArray(u) || u.length < 3 || !u.every((m) => Array.isArray(m) && m.length === 2 && m.every(Number.isFinite))) throw 0;
44
- k(!1), s(u);
45
- } catch {
46
- k(!0);
47
- }
48
- }, h = (a) => {
49
- const u = parseFloat(a);
50
- return Number.isFinite(u) ? u : 0;
51
- };
52
- return /* @__PURE__ */ e.createElement("div", { className: "bse", "data-testid": "bed-shape-editor" }, /* @__PURE__ */ e.createElement("div", { className: "bse-modes" }, [["rect", "Rectangular"], ["circle", "Circular"], ["custom", "Custom"]].map(([a, u]) => /* @__PURE__ */ e.createElement("button", { key: a, type: "button", className: r === a ? "on" : "", disabled: n, onClick: () => o(a) }, u))), r === "rect" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "Width ", /* @__PURE__ */ e.createElement(
53
- "input",
54
- {
55
- type: "number",
56
- min: "1",
57
- value: p.w,
58
- disabled: n,
59
- "data-testid": "bed-w",
60
- onChange: (a) => v({ ...p, w: h(a.target.value) })
61
- }
62
- ), " mm"), /* @__PURE__ */ e.createElement("label", null, "Depth ", /* @__PURE__ */ e.createElement(
63
- "input",
64
- {
65
- type: "number",
66
- min: "1",
67
- value: p.d,
68
- disabled: n,
69
- "data-testid": "bed-d",
70
- onChange: (a) => v({ ...p, d: h(a.target.value) })
71
- }
72
- ), " mm"), /* @__PURE__ */ e.createElement("label", null, "Origin X ", /* @__PURE__ */ e.createElement(
73
- "input",
74
- {
75
- type: "number",
76
- value: p.ox,
77
- disabled: n,
78
- onChange: (a) => v({ ...p, ox: h(a.target.value) })
79
- }
80
- )), /* @__PURE__ */ e.createElement("label", null, "Origin Y ", /* @__PURE__ */ e.createElement(
81
- "input",
82
- {
83
- type: "number",
84
- value: p.oy,
85
- disabled: n,
86
- onChange: (a) => v({ ...p, oy: h(a.target.value) })
87
- }
88
- ))), r === "circle" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "Diameter ", /* @__PURE__ */ e.createElement(
89
- "input",
90
- {
91
- type: "number",
92
- min: "1",
93
- value: E,
94
- disabled: n,
95
- "data-testid": "bed-dia",
96
- onChange: (a) => M(h(a.target.value))
97
- }
98
- ), " mm")), r === "custom" && /* @__PURE__ */ e.createElement(
99
- "textarea",
100
- {
101
- className: "bse-json" + (y ? " err" : ""),
102
- rows: 3,
103
- value: d,
104
- disabled: n,
105
- "data-testid": "bed-json",
106
- onChange: (a) => A(a.target.value),
107
- placeholder: "[[0,0],[200,0],[200,200],[0,200]]"
108
- }
109
- ));
110
- }
111
- const Q = {
112
- printable_area: H
113
- }, D = Object.keys(_).filter((t) => _[t].length > 0), X = ["all", "simple", "advanced", "expert", "develop"], Y = (t) => t.replace("::build", "").replace("Tab", "").replace("_sla", " (SLA)"), V = (t) => /SLA|build_sla\b/.test(t) ? "SLA" : "FFF";
114
- function Z(t, s) {
115
- var c;
116
- if (!t || !(s in t)) return !1;
117
- const n = (c = S[s]) == null ? void 0 : c.default, i = Array.isArray(n) ? n[0] : n;
118
- return String(t[s]) !== String(i ?? "");
119
- }
120
- function K(t, s) {
121
- var i;
122
- if (s === "all" || t.startsWith("<")) return !0;
123
- const n = (i = S[t]) == null ? void 0 : i.mode;
124
- return s === "simple" ? n === "simple" : s === "advanced" ? n === "simple" || n === "advanced" : !0;
125
- }
126
- function ee(t) {
127
- return t ? t.gui_type === "color" ? "color" : t.enum_values ? "select" : t.type === "coBool" || t.type === "coBools" ? "checkbox" : t.multiline || t.is_code ? "textarea" : t.type === "coPoints" || t.type === "coPoint" ? "points" : "input" : "unknown";
128
- }
129
- function te({ mode: t }) {
130
- return /* @__PURE__ */ e.createElement("span", { className: `badge mode-${t ?? "none"}` }, t ?? "—");
131
- }
132
- function ae({ def: t, optKey: s, settings: n, setSettings: i, disabled: c, customWidgets: r }) {
133
- const o = T(n, s), p = (d) => i((x) => ({ ...x, [s]: d })), b = (r == null ? void 0 : r[s]) ?? Q[s];
134
- if (b) return /* @__PURE__ */ e.createElement(b, { def: t, value: o, onChange: p, disabled: c });
135
- const E = ee(t), g = Array.isArray(o) ? o[0] : o;
136
- switch (E) {
137
- case "select":
138
- return /* @__PURE__ */ e.createElement("select", { value: g ?? "", onChange: (d) => p(d.target.value), disabled: c }, t.enum_values.map((d, x) => {
139
- var y;
140
- return /* @__PURE__ */ e.createElement("option", { key: d, value: d }, ((y = t.enum_labels) == null ? void 0 : y[x]) ?? d);
141
- }));
142
- case "checkbox":
143
- return /* @__PURE__ */ e.createElement("input", { type: "checkbox", checked: Array.isArray(o) ? !!o[0] : !!o, onChange: (d) => p(d.target.checked), disabled: c });
144
- case "color":
145
- return /* @__PURE__ */ e.createElement("input", { type: "color", value: typeof g == "string" && g.startsWith("#") ? g : "#00ae42", onChange: (d) => p(d.target.value), disabled: c });
146
- case "textarea":
147
- return /* @__PURE__ */ e.createElement("textarea", { rows: 2, value: Array.isArray(o) ? o.join(`
148
- `) : o ?? "", onChange: (d) => p(d.target.value), disabled: c });
149
- case "points":
150
- return /* @__PURE__ */ e.createElement("code", { className: "pts" }, JSON.stringify(o ?? []));
151
- case "unknown":
152
- return /* @__PURE__ */ e.createElement("span", { className: "muted" }, "No schema");
153
- default:
154
- return /* @__PURE__ */ e.createElement("span", { className: "inputwrap" }, /* @__PURE__ */ e.createElement("input", { value: g ?? "", onChange: (d) => p(d.target.value), disabled: c }), t.sidetext && /* @__PURE__ */ e.createElement("span", { className: "unit" }, t.sidetext));
155
- }
156
- }
157
- function ne({ optKey: t, settings: s, setSettings: n, disabled: i, onOptionOpen: c, customWidgets: r }) {
158
- if (t.startsWith("<")) return /* @__PURE__ */ e.createElement("div", { className: "row custom" }, /* @__PURE__ */ e.createElement("span", { className: "muted" }, "⚙ Custom widget ", t));
159
- const o = S[t], p = i ? i[t] : void 0, b = p != null, E = Z(s, t), g = () => n((x) => {
160
- const y = { ...x };
161
- return delete y[t], y;
162
- }), d = (o == null ? void 0 : o.label) || (o == null ? void 0 : o.full_label) || t;
163
- return /* @__PURE__ */ e.createElement("div", { className: "row" + (b ? " disabled" : ""), title: b ? `Disabled when: ${p}` : (o == null ? void 0 : o.tooltip) ?? "", "data-testid": `row-${t}` }, /* @__PURE__ */ e.createElement("span", { className: "lbl-cell" }, E && /* @__PURE__ */ e.createElement("span", { className: "dirty-dot", title: "Changed from default", "data-testid": `dirty-${t}` }), c ? /* @__PURE__ */ e.createElement("button", { type: "button", className: "lbl lbl-link", onClick: () => c(t), title: "Details" }, d) : /* @__PURE__ */ e.createElement("span", { className: "lbl" }, d)), /* @__PURE__ */ e.createElement("code", { className: "key" }, t), /* @__PURE__ */ e.createElement(te, { mode: o == null ? void 0 : o.mode }), /* @__PURE__ */ e.createElement("span", { className: "widget-cell" }, /* @__PURE__ */ e.createElement(ae, { def: o, optKey: t, settings: s, setSettings: n, disabled: b, customWidgets: r }), E && /* @__PURE__ */ e.createElement("button", { className: "reset-btn", onClick: g, title: "Reset to default", "data-testid": `reset-${t}` }, "↺")));
164
- }
165
- function ce({ settings: t, setSettings: s, onOptionOpen: n, embedded: i = !1, customWidgets: c, only: r }) {
166
- const [o, p] = w((r == null ? void 0 : r.builder) ?? D[0] ?? ""), [b, E] = w(0), [g, d] = w("all"), [x, y] = w(""), k = I(t), v = D.filter((l) => V(l) === k), M = (r == null ? void 0 : r.builder) ?? (v.includes(o) ? o : v[0] ?? ""), A = _[(r == null ? void 0 : r.builder) ?? M] ?? [], h = r != null && r.page ? A.filter((l) => l.page === r.page) : A, a = h[Math.min(b, h.length - 1)], u = F(() => $(U(t)), [t]), m = x.trim().toLowerCase(), N = F(() => m ? Object.entries(S).filter(([l, f]) => {
167
- var C, j;
168
- return l.includes(m) || ((C = f.label) == null ? void 0 : C.toLowerCase().includes(m)) || ((j = f.full_label) == null ? void 0 : j.toLowerCase().includes(m));
169
- }).slice(0, 60).map(([l]) => l) : [], [m]), z = (l, f) => /* @__PURE__ */ e.createElement(ne, { key: l + f, optKey: l, settings: t, setSettings: s, disabled: u, onOptionOpen: n, customWidgets: c });
170
- return /* @__PURE__ */ e.createElement(W, { css: q, className: i ? "sp-embedded" : void 0 }, /* @__PURE__ */ e.createElement("div", { className: "settings-panel" }, !r && /* @__PURE__ */ e.createElement("div", { className: "sp-top" }, /* @__PURE__ */ e.createElement("input", { className: "sp-search", placeholder: `Search options (${Object.keys(S).length})…`, value: x, onChange: (l) => y(l.target.value), "data-testid": "opt-search" }), !m && /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement("label", { className: "sp-builder" }, /* @__PURE__ */ e.createElement("span", null, "Settings group"), /* @__PURE__ */ e.createElement("select", { value: M, onChange: (l) => {
171
- p(l.target.value), E(0);
172
- } }, v.map((l) => /* @__PURE__ */ e.createElement("option", { key: l, value: l }, Y(l))))), /* @__PURE__ */ e.createElement("div", { className: "sp-pages" }, h.map((l, f) => /* @__PURE__ */ e.createElement("button", { key: l.page + f, className: f === Math.min(b, h.length - 1) ? "on" : "", onClick: () => E(f) }, l.page))), /* @__PURE__ */ e.createElement("div", { className: "sp-modes" }, X.map((l) => /* @__PURE__ */ e.createElement("button", { key: l, className: g === l ? "on" : "", onClick: () => d(l) }, l))))), /* @__PURE__ */ e.createElement("div", { className: "sp-body" }, m ? /* @__PURE__ */ e.createElement("section", { "data-testid": "search-results" }, /* @__PURE__ */ e.createElement("h3", null, "Search: “", x, "” — ", N.length, " hit", N.length === 1 ? "" : "s", N.length === 60 ? "+" : ""), N.map(z), !N.length && /* @__PURE__ */ e.createElement("p", { className: "muted" }, "No matches")) : a ? /* @__PURE__ */ e.createElement(e.Fragment, null, !r && /* @__PURE__ */ e.createElement("h2", null, a.page, " ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, "Tab.cpp:", a.line)), a.groups.map((l) => {
173
- const f = l.options.filter((C) => K(C, g) && !(r && C.startsWith("<")));
174
- return f.length ? /* @__PURE__ */ e.createElement("section", { key: l.group + l.line }, /* @__PURE__ */ e.createElement("h3", null, l.group || "(untitled group)", " ", /* @__PURE__ */ e.createElement("span", { className: "muted" }, ":", l.line)), f.map(z)) : null;
175
- })) : /* @__PURE__ */ e.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 });
176
9
  }
177
10
  export {
178
- ce as default
11
+ l as default,
12
+ d as schema,
13
+ f as toggle,
14
+ h as uiTree
179
15
  };