three-slicer 0.1.7 → 0.2.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.
- package/CHANGELOG.md +35 -0
- package/README.md +58 -3
- package/THIRD-PARTY-NOTICES.md +37 -0
- package/components/README.md +2 -0
- package/components/dist/SettingsPanel.js +92 -92
- package/data/config-schema.json +698 -0
- package/data/printers.json +1 -1
- package/data/ui-tree.json +342 -0
- package/engine/README.md +20 -0
- package/engine/examples/sla_headless.mjs +51 -0
- package/engine/index.d.ts +3 -0
- package/engine/index.js +6 -0
- package/engine/src/client.js +31 -2
- package/engine/src/settings.js +164 -0
- package/engine/src/sla_core.js +188 -0
- package/engine/src/sla_request.js +185 -0
- package/engine/src/slicer.worker.js +63 -1
- package/engine/src/slicer_core.js +0 -0
- package/engine/src/slicer_core.mt.js +0 -0
- package/package.json +8 -3
- package/types/client.d.ts +14 -0
- package/types/settings-keys.d.ts +107 -1
- package/types/settings.d.ts +34 -0
- package/types/sla.d.ts +167 -0
- package/types/viewer.d.ts +2 -0
- package/types/worker.d.ts +9 -1
- package/viewer/README.md +7 -1
- package/viewer/dist/Viewport.js +3454 -3138
- package/viewer/dist/model_loaders.js +1 -1
- package/viewer/dist/parse_3mf-C2X_W442.js +300 -0
- package/viewer/dist/parse_3mf.worker.js +1 -1
- package/viewer/dist/parse_3mf-8oCnCJNK.js +0 -216
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 — 2026-08-19
|
|
4
|
+
|
|
5
|
+
SLA (resin) printing lands as a second technology in the same kernel, routed by `printer_technology`.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- SLA slicing in the WASM kernel: `slicer.sliceSla(stl, params)` on the engine handle,
|
|
10
|
+
`client.sliceSla` / `client.sliceSlaJob` on the worker client. Results carry per-layer mask segment
|
|
11
|
+
streams, generated support and pad meshes, `resin_ml` / `time_estimate` stats and `lift_layers`
|
|
12
|
+
(pad + elevation) — no G-code.
|
|
13
|
+
- The support chain is PrusaSlicer 2.9.6's own, ported verbatim: support-point generator
|
|
14
|
+
(SupportPointGenerator + SupportIslands), the default support tree, and the real pad geometry. Built
|
|
15
|
+
against vendored NLopt 2.5.0, Prusa's bundled libigl, SGI glu-libtess, and CGAL headers.
|
|
16
|
+
- `deriveSlaParams(settings)`, `printerTechnology(settings)`, `resinCatalog` / `resinSettingsFor(name)`
|
|
17
|
+
in `three-slicer/settings`; SLA machine profiles and the resin material catalog in the data files.
|
|
18
|
+
- Typed capability errors instead of silent approximation: `SLA_UNSUPPORTED_HOLLOWING` (hollowing/drain
|
|
19
|
+
holes), `SLA_UNSUPPORTED_ORGANIC` (organic trees), `SLA_PAD_AROUND_OBJECT_UNSUPPORTED` (zero-elevation
|
|
20
|
+
pad embedding). `SLA_CAPABILITIES` from `three-slicer/client` is the machine-readable map.
|
|
21
|
+
- Viewer: an SLA printer profile swaps the filament card for the resin card, previews the support/pad
|
|
22
|
+
meshes with the lifted layer frame, and exports `.sl1` archives — portrait PNG masks (the SL1 family's
|
|
23
|
+
panel mounting) plus upstream's `config.ini` field set.
|
|
24
|
+
- `<SettingsPanel/>` renders the SLA tabs for an SLA profile — no prop, it follows the settings map.
|
|
25
|
+
- `.3mf` SLA records (manual support points, drain holes) survive import/export round-trip.
|
|
26
|
+
- A runnable SLA example: `node node_modules/three-slicer/engine/examples/sla_headless.mjs`.
|
|
27
|
+
- `THIRD-PARTY-NOTICES.md` documenting the licenses compiled into the shipped kernels.
|
|
28
|
+
|
|
29
|
+
### Notes
|
|
30
|
+
|
|
31
|
+
- The multithreaded kernel carries the SLA path too and is pinned byte-identical to the single-threaded
|
|
32
|
+
one over the same SLA slice.
|
|
33
|
+
- FFF output is unchanged: the golden byte-identical G-code check holds across this release.
|
|
34
|
+
|
|
35
|
+
Earlier releases (0.1.x) predate this changelog.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# three-slicer
|
|
2
2
|
|
|
3
|
-
Browser/WASM 3D-printing slicer package derived from [OrcaSlicer](https://github.com/OrcaSlicer/OrcaSlicer). It can slice binary STL files to G-code in Node or the browser, and it also ships React UI pieces for building a browser slicer: a three.js viewport, GPU toolpath preview, and a schema-driven settings panel.
|
|
3
|
+
Browser/WASM 3D-printing slicer package derived from [OrcaSlicer](https://github.com/OrcaSlicer/OrcaSlicer). It can slice binary STL files to G-code in Node or the browser, and it also ships React UI pieces for building a browser slicer: a three.js viewport, GPU toolpath preview, and a schema-driven settings panel. SLA (resin) printers are a second technology in the same kernel — supports and pad from PrusaSlicer's ported chain, previewed in the same viewer, exported as an `.sl1` archive.
|
|
4
4
|
|
|
5
5
|
The package is published as a single npm package, `three-slicer`, with subpath exports for the engine, viewer, components, worker, and extracted OrcaSlicer data.
|
|
6
6
|
|
|
@@ -23,6 +23,7 @@ The package is published as a single npm package, `three-slicer`, with subpath e
|
|
|
23
23
|
- Support painting and material painting, plus per-tool filament and purge statistics.
|
|
24
24
|
- 3MF **project** import in the viewer: a slicer-written `.3mf` (OrcaSlicer/BambuStudio save, MakerWorld download) restores its plate layout, project settings, and painted facets — not just the meshes.
|
|
25
25
|
- Extracted data files for custom UIs: config schema, UI tree, toggle rules, invalidation map, and the printer/process/filament catalogs.
|
|
26
|
+
- SLA (resin) slicing: PrusaSlicer 2.9.6's ported support-point generator, support tree and pad, a resin material catalog, and `.sl1` mask export — routed by `printer_technology`, with typed capability errors for what the port does not cover.
|
|
26
27
|
- Automatic multithreaded WASM selection on cross-origin-isolated browser pages.
|
|
27
28
|
|
|
28
29
|
## Installation
|
|
@@ -256,8 +257,9 @@ const result = slicer.slice(stlArrayBuffer, params)
|
|
|
256
257
|
## 3MF Projects
|
|
257
258
|
|
|
258
259
|
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
|
|
260
|
-
|
|
260
|
+
preset its author sliced with. `<Viewport/>` reads all of it on import. The project parser/writer currently remain
|
|
261
|
+
viewer internals; there is no `three-slicer/viewer/project` public export yet. Hosts can normalize or serialize a
|
|
262
|
+
raw project settings object with the public settings helpers below, but should not import viewer source files.
|
|
261
263
|
|
|
262
264
|
**Every value in that file is a string**, and reading it raw is not a cosmetic problem — a disabled option is the
|
|
263
265
|
string `"0"`, and `"0"` is truthy, so a raw import turns every disabled option ON. A point is the string `"256x256"`
|
|
@@ -398,6 +400,52 @@ worker.postMessage({ cmd: 'erase', facet, hx, hy, hz, cx, cy, cz, radius })
|
|
|
398
400
|
|
|
399
401
|
Because a single enum serves both jobs, a support blocker paint and an Extruder 2 paint are the same mark on a facet, and painted materials only take effect with support turned off (the painted multi-material path emits no support). Support painting is unaffected: a support-enabled slice stays on the single-material path.
|
|
400
402
|
|
|
403
|
+
## SLA (Resin) Slicing
|
|
404
|
+
|
|
405
|
+
SLA is a second printer technology, not an FFF mode. `printer_technology` in the settings map routes it —
|
|
406
|
+
apply an SLA machine profile from the catalog (`printersByVendor` marks the technology) or set the key
|
|
407
|
+
yourself, then derive SLA params and call the SLA entry:
|
|
408
|
+
|
|
409
|
+
```js
|
|
410
|
+
import { printerTechnology, deriveSlaParams, resinCatalog, resinSettingsFor } from 'three-slicer/settings'
|
|
411
|
+
|
|
412
|
+
printerTechnology(settings) // 'SLA' or 'FFF'
|
|
413
|
+
const params = deriveSlaParams(settings) // layers/exposure, display, supports, pad, raster orientation
|
|
414
|
+
|
|
415
|
+
const result = slicer.sliceSla(stl, params)
|
|
416
|
+
result.stats.sla // true — no G-code; stats carry resin_ml and time_estimate
|
|
417
|
+
result.stats.layers // includes the pad + elevation lift below the model
|
|
418
|
+
result.layers[0].paths // the same stride-8 segment stream the FFF preview reads
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
In a worker, `client.sliceSla(stl, params, { onProgress, onLayer })` is the same call over the message
|
|
422
|
+
protocol, and `client.sliceSlaJob(job)` takes the typed job protocol — objects with manual support points,
|
|
423
|
+
drain-hole records and modifier volumes — validated structurally before slicing.
|
|
424
|
+
|
|
425
|
+
A runnable version ships with the package: `node node_modules/three-slicer/engine/examples/sla_headless.mjs`
|
|
426
|
+
slices a cube with supports and pad, then demonstrates the capability refusal below.
|
|
427
|
+
|
|
428
|
+
The support chain is PrusaSlicer 2.9.6's own, ported verbatim and compiled into the same WASM kernel: the
|
|
429
|
+
support-point generator, the default support tree, and the real pad geometry. Resin materials follow the
|
|
430
|
+
same catalog pattern as filaments: `resinCatalog` lists them, `resinSettingsFor(name)` is one preset.
|
|
431
|
+
|
|
432
|
+
**What the kernel cannot do it refuses with a typed code — it never approximates.** A request the port does
|
|
433
|
+
not cover fails with a stable error instead of silently producing something else:
|
|
434
|
+
|
|
435
|
+
| Code | Refused request |
|
|
436
|
+
| --- | --- |
|
|
437
|
+
| `SLA_UNSUPPORTED_HOLLOWING` | `hollowing_enable` or drain holes — a solid slice answered to a hollow request would be a mislabeled print |
|
|
438
|
+
| `SLA_UNSUPPORTED_ORGANIC` | organic support trees |
|
|
439
|
+
|
|
440
|
+
`SLA_CAPABILITIES` (exported from `three-slicer/client`) is the machine-readable capability map. A `.3mf`
|
|
441
|
+
carrying SLA records survives round-trip regardless: manual support points and drain holes are preserved on
|
|
442
|
+
import and written back on export, even where slicing refuses to consume them.
|
|
443
|
+
|
|
444
|
+
The viewer follows the technology automatically: an SLA profile swaps the filament card for the resin card,
|
|
445
|
+
hides the FFF-only tools (prime tower, painting brushes), previews the generated support and pad meshes, and
|
|
446
|
+
exports an `.sl1` archive — per-layer PNG masks in the SL1 family's portrait orientation plus upstream's
|
|
447
|
+
`config.ini` — instead of G-code.
|
|
448
|
+
|
|
401
449
|
## Worker Usage
|
|
402
450
|
|
|
403
451
|
Slicing blocks its thread for seconds, so in a browser it belongs in a worker. `three-slicer/client` wraps the
|
|
@@ -568,6 +616,7 @@ the layout by hand.
|
|
|
568
616
|
| --- | --- |
|
|
569
617
|
| `createSlicer()` | Loads the WASM kernel and returns a slicer handle |
|
|
570
618
|
| `slicer.slice(stl, params, callbacks?)` | Slices binary STL input to G-code or streamed layers |
|
|
619
|
+
| `slicer.sliceSla(stl, params, onProgress?)` | SLA slice — layer masks, support/pad meshes, resin stats; typed `error` on a refused capability |
|
|
571
620
|
| `slicer.paintPrepare(stl)` | Prepares painting against a model; returns the facet count |
|
|
572
621
|
| `slicer.paint(args)` | Paints enforcer/blocker data (boolean pair; the state-addressed form is worker-only) |
|
|
573
622
|
| `slicer.paintClear()` | Clears painting data for every state |
|
|
@@ -597,6 +646,9 @@ the layout by hand.
|
|
|
597
646
|
| `machineLimitKeys` | The schema keys the kernel's machine limits are read from |
|
|
598
647
|
| `processPresets()` | Lazy facade over the print (process) preset catalog |
|
|
599
648
|
| `filamentPresets()` | Lazy facade over the material preset catalog |
|
|
649
|
+
| `printerTechnology(settings)` | `'SLA'` or `'FFF'` — what routes a slice to `slice_sla` |
|
|
650
|
+
| `deriveSlaParams(settings)` | Sparse settings → the SLA kernel's params (display, supports, pad, raster) |
|
|
651
|
+
| `resinCatalog` / `resinSettingsFor(name)` | The resin material catalog, and one preset's settings |
|
|
600
652
|
|
|
601
653
|
### `three-slicer/toggle`
|
|
602
654
|
|
|
@@ -648,6 +700,9 @@ the layout by hand.
|
|
|
648
700
|
- Some vector settings are simplified to their first element.
|
|
649
701
|
- Material painting and support cannot currently produce two materials on the same slice: the painted multi-material path emits no support, and one triangle selector serves both brushes.
|
|
650
702
|
- The multi-material prime tower is a real ported wipe tower, but the fallback square ring used when it fails is not.
|
|
703
|
+
- SLA hollowing and drain-hole geometry are refused with `SLA_UNSUPPORTED_HOLLOWING` (the OpenVDB chain is not ported); the records still round-trip through `.3mf`.
|
|
704
|
+
- The `.sl1` export lives in the viewer (its masks need a canvas); there is no headless `.sl1` writer export yet.
|
|
705
|
+
- SLA mask edges are canvas anti-aliasing, not upstream's AGG rasterizer — same geometry, slightly different edge pixels.
|
|
651
706
|
- Multithreaded WASM requires cross-origin isolation.
|
|
652
707
|
- `three` is pinned as a peer dependency because viewer internals depend on the `TransformControls` API shape.
|
|
653
708
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
`three-slicer` is licensed AGPL-3.0-or-later (see `LICENSE.txt`). The distributed artifacts — the WASM
|
|
4
|
+
kernels embedded in `engine/src/slicer_core.js` / `slicer_core.mt.js` and the bundled viewer/components —
|
|
5
|
+
contain compiled or bundled code from the following projects. Full license texts ship in the source
|
|
6
|
+
repository at the paths given (the package's `repository` field points there); the kernel's vendored copies
|
|
7
|
+
live under `packages/wasm-core/third_party/deps_src/`.
|
|
8
|
+
|
|
9
|
+
## Compiled into the WASM kernels
|
|
10
|
+
|
|
11
|
+
| Component | License | Source of the vendored copy |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| OrcaSlicer (the FFF slicing code this package is ported from) | AGPL-3.0-or-later | github.com/SoftFever/OrcaSlicer |
|
|
14
|
+
| PrusaSlicer 2.9.6 (the SLA support/pad chain, ported verbatim under `packages/wasm-core/slasupport_port/`) | AGPL-3.0-or-later | github.com/prusa3d/PrusaSlicer |
|
|
15
|
+
| Clipper (Angus Johnson) | BSL-1.0 | `deps_src/clipper/` |
|
|
16
|
+
| Clipper2 | BSL-1.0 | `deps_src/clipper2/` |
|
|
17
|
+
| Boost (header-only use) | BSL-1.0 | headers at build time |
|
|
18
|
+
| Eigen (header-only) | MPL-2.0 | headers at build time |
|
|
19
|
+
| CGAL 6 (header-only use, GMP/MPFR disabled) | GPL-3.0-or-later | headers at build time |
|
|
20
|
+
| libigl (headers; both the Orca-generation copy and Prusa's bundled copy) | MPL-2.0 | `deps_src/libigl/`, `deps_src/libigl_prusa/` |
|
|
21
|
+
| libnest2d (headers) | LGPL-3.0 | `deps_src/libnest2d/` (its `LICENSE.txt`) |
|
|
22
|
+
| NLopt 2.5.0 | LGPL-2.1-or-later for the combined library (`deps_src/nlopt/COPYING`); per-algorithm terms in each `algs/*/COPYRIGHT` — the most restrictive is `algs/luksan` (LGPL-2.1+), most others (e.g. `algs/newuoa`) are MIT-style Powell/MIT notices | `deps_src/nlopt/` |
|
|
23
|
+
| SGI glu-libtess (the GLU tessellator) | SGI Free Software License B 2.0 | `deps_src/glu-libtess/` (header of each source file) |
|
|
24
|
+
| Emscripten runtime (the generated JS glue around the kernels) | MIT | emscripten.org |
|
|
25
|
+
|
|
26
|
+
## Bundled into the viewer/components builds
|
|
27
|
+
|
|
28
|
+
| Component | License | Note |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| fflate (via three.js examples) | MIT | zip/unzip in the 3mf and SL1 codecs |
|
|
31
|
+
|
|
32
|
+
`three`, `react` and `react-dom` are peer dependencies and are not bundled or redistributed by this package.
|
|
33
|
+
|
|
34
|
+
The LGPL components (NLopt's luksan directory, libnest2d headers) are compiled into a work distributed under
|
|
35
|
+
AGPL-3.0-or-later, which satisfies their license terms; their complete license texts accompany the source
|
|
36
|
+
repository as noted above. Nothing in this file overrides a component's own license — where this summary and
|
|
37
|
+
a vendored license file disagree, the vendored file is authoritative.
|
package/components/README.md
CHANGED
|
@@ -6,6 +6,8 @@ Reusable React components for the browser slicer. Props-driven — no global sta
|
|
|
6
6
|
|
|
7
7
|
An OrcaSlicer-style settings form generated from `three-slicer/data` (schema + UI tree + toggle rules): tab/page/group navigation, mode filter (simple/advanced/expert), search across 923 options, dirty markers with per-option reset, and enable/disable rules evaluation.
|
|
8
8
|
|
|
9
|
+
The tab set follows the printer profile's technology: a settings map whose `printer_technology` says SLA swaps the FFF tabs for the SLA ones (print/material/printer builders carry the technology in their upstream builder name, which is what the filter reads). No prop needed — change the profile in the shared settings map and the panel follows.
|
|
10
|
+
|
|
9
11
|
```bash
|
|
10
12
|
npm i three-slicer # react is a peer dependency — npm installs it for you
|
|
11
13
|
```
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
import e, { useRef as
|
|
2
|
-
import { createPortal as
|
|
3
|
-
import { uiTree as
|
|
4
|
-
import { settingRaw as
|
|
5
|
-
import { disabledKeys as
|
|
6
|
-
function
|
|
7
|
-
const i =
|
|
8
|
-
const
|
|
9
|
-
return
|
|
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
10
|
});
|
|
11
|
-
return
|
|
12
|
-
const
|
|
11
|
+
return P(() => {
|
|
12
|
+
const r = i.current, o = r.shadowRoot || r.attachShadow({ mode: "open" });
|
|
13
13
|
let p = o.querySelector("style[data-ts-css]");
|
|
14
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 },
|
|
15
|
+
}, [t, c]), /* @__PURE__ */ e.createElement("div", { ref: i, className: s }, B(n, c));
|
|
16
16
|
}
|
|
17
|
-
const
|
|
18
|
-
const s = t / 2,
|
|
19
|
-
return Array.from({ length:
|
|
20
|
-
const
|
|
21
|
-
return [+(s + s * Math.cos(
|
|
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
22
|
});
|
|
23
|
-
},
|
|
24
|
-
const s = t.map((i) => i[0]),
|
|
25
|
-
return { x0: Math.min(...s), y0: Math.min(...
|
|
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
26
|
};
|
|
27
|
-
function
|
|
28
|
-
const i = Array.isArray(t) && t.length >= 3 ? t :
|
|
29
|
-
|
|
30
|
-
const a =
|
|
31
|
-
|
|
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
32
|
}, [JSON.stringify(t)]);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const { w:
|
|
36
|
-
|
|
37
|
-
}, k = (a) => {
|
|
38
|
-
u(a), a > 0 && s(T(a));
|
|
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));
|
|
39
37
|
}, M = (a) => {
|
|
40
|
-
|
|
38
|
+
g(a), a > 0 && s(G(a));
|
|
39
|
+
}, A = (a) => {
|
|
40
|
+
x(a);
|
|
41
41
|
try {
|
|
42
|
-
const
|
|
43
|
-
if (!Array.isArray(
|
|
44
|
-
|
|
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
45
|
} catch {
|
|
46
|
-
|
|
46
|
+
k(!0);
|
|
47
47
|
}
|
|
48
|
-
},
|
|
49
|
-
const
|
|
50
|
-
return Number.isFinite(
|
|
48
|
+
}, h = (a) => {
|
|
49
|
+
const u = parseFloat(a);
|
|
50
|
+
return Number.isFinite(u) ? u : 0;
|
|
51
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,
|
|
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
53
|
"input",
|
|
54
54
|
{
|
|
55
55
|
type: "number",
|
|
56
56
|
min: "1",
|
|
57
57
|
value: p.w,
|
|
58
|
-
disabled:
|
|
58
|
+
disabled: n,
|
|
59
59
|
"data-testid": "bed-w",
|
|
60
|
-
onChange: (a) =>
|
|
60
|
+
onChange: (a) => v({ ...p, w: h(a.target.value) })
|
|
61
61
|
}
|
|
62
62
|
), " mm"), /* @__PURE__ */ e.createElement("label", null, "Depth ", /* @__PURE__ */ e.createElement(
|
|
63
63
|
"input",
|
|
@@ -65,84 +65,84 @@ function U({ value: t, onChange: s, disabled: r }) {
|
|
|
65
65
|
type: "number",
|
|
66
66
|
min: "1",
|
|
67
67
|
value: p.d,
|
|
68
|
-
disabled:
|
|
68
|
+
disabled: n,
|
|
69
69
|
"data-testid": "bed-d",
|
|
70
|
-
onChange: (a) =>
|
|
70
|
+
onChange: (a) => v({ ...p, d: h(a.target.value) })
|
|
71
71
|
}
|
|
72
72
|
), " mm"), /* @__PURE__ */ e.createElement("label", null, "Origin X ", /* @__PURE__ */ e.createElement(
|
|
73
73
|
"input",
|
|
74
74
|
{
|
|
75
75
|
type: "number",
|
|
76
76
|
value: p.ox,
|
|
77
|
-
disabled:
|
|
78
|
-
onChange: (a) =>
|
|
77
|
+
disabled: n,
|
|
78
|
+
onChange: (a) => v({ ...p, ox: h(a.target.value) })
|
|
79
79
|
}
|
|
80
80
|
)), /* @__PURE__ */ e.createElement("label", null, "Origin Y ", /* @__PURE__ */ e.createElement(
|
|
81
81
|
"input",
|
|
82
82
|
{
|
|
83
83
|
type: "number",
|
|
84
84
|
value: p.oy,
|
|
85
|
-
disabled:
|
|
86
|
-
onChange: (a) =>
|
|
85
|
+
disabled: n,
|
|
86
|
+
onChange: (a) => v({ ...p, oy: h(a.target.value) })
|
|
87
87
|
}
|
|
88
|
-
))),
|
|
88
|
+
))), r === "circle" && /* @__PURE__ */ e.createElement("div", { className: "bse-grid" }, /* @__PURE__ */ e.createElement("label", null, "Diameter ", /* @__PURE__ */ e.createElement(
|
|
89
89
|
"input",
|
|
90
90
|
{
|
|
91
91
|
type: "number",
|
|
92
92
|
min: "1",
|
|
93
|
-
value:
|
|
94
|
-
disabled:
|
|
93
|
+
value: E,
|
|
94
|
+
disabled: n,
|
|
95
95
|
"data-testid": "bed-dia",
|
|
96
|
-
onChange: (a) =>
|
|
96
|
+
onChange: (a) => M(h(a.target.value))
|
|
97
97
|
}
|
|
98
|
-
), " mm")),
|
|
98
|
+
), " mm")), r === "custom" && /* @__PURE__ */ e.createElement(
|
|
99
99
|
"textarea",
|
|
100
100
|
{
|
|
101
|
-
className: "bse-json" + (
|
|
101
|
+
className: "bse-json" + (y ? " err" : ""),
|
|
102
102
|
rows: 3,
|
|
103
103
|
value: d,
|
|
104
|
-
disabled:
|
|
104
|
+
disabled: n,
|
|
105
105
|
"data-testid": "bed-json",
|
|
106
|
-
onChange: (a) =>
|
|
106
|
+
onChange: (a) => A(a.target.value),
|
|
107
107
|
placeholder: "[[0,0],[200,0],[200,200],[0,200]]"
|
|
108
108
|
}
|
|
109
109
|
));
|
|
110
110
|
}
|
|
111
|
-
const
|
|
112
|
-
printable_area:
|
|
113
|
-
},
|
|
114
|
-
function
|
|
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
115
|
var c;
|
|
116
116
|
if (!t || !(s in t)) return !1;
|
|
117
|
-
const
|
|
117
|
+
const n = (c = S[s]) == null ? void 0 : c.default, i = Array.isArray(n) ? n[0] : n;
|
|
118
118
|
return String(t[s]) !== String(i ?? "");
|
|
119
119
|
}
|
|
120
|
-
function
|
|
120
|
+
function K(t, s) {
|
|
121
121
|
var i;
|
|
122
122
|
if (s === "all" || t.startsWith("<")) return !0;
|
|
123
|
-
const
|
|
124
|
-
return s === "simple" ?
|
|
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
125
|
}
|
|
126
|
-
function
|
|
126
|
+
function ee(t) {
|
|
127
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
128
|
}
|
|
129
|
-
function
|
|
129
|
+
function te({ mode: t }) {
|
|
130
130
|
return /* @__PURE__ */ e.createElement("span", { className: `badge mode-${t ?? "none"}` }, t ?? "—");
|
|
131
131
|
}
|
|
132
|
-
function
|
|
133
|
-
const o =
|
|
134
|
-
if (
|
|
135
|
-
const
|
|
136
|
-
switch (
|
|
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
137
|
case "select":
|
|
138
|
-
return /* @__PURE__ */ e.createElement("select", { value:
|
|
139
|
-
var
|
|
140
|
-
return /* @__PURE__ */ e.createElement("option", { key: d, value: d }, ((
|
|
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
141
|
}));
|
|
142
142
|
case "checkbox":
|
|
143
143
|
return /* @__PURE__ */ e.createElement("input", { type: "checkbox", checked: Array.isArray(o) ? !!o[0] : !!o, onChange: (d) => p(d.target.checked), disabled: c });
|
|
144
144
|
case "color":
|
|
145
|
-
return /* @__PURE__ */ e.createElement("input", { type: "color", value: typeof
|
|
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
146
|
case "textarea":
|
|
147
147
|
return /* @__PURE__ */ e.createElement("textarea", { rows: 2, value: Array.isArray(o) ? o.join(`
|
|
148
148
|
`) : o ?? "", onChange: (d) => p(d.target.value), disabled: c });
|
|
@@ -151,29 +151,29 @@ function V({ def: t, optKey: s, settings: r, setSettings: i, disabled: c, custom
|
|
|
151
151
|
case "unknown":
|
|
152
152
|
return /* @__PURE__ */ e.createElement("span", { className: "muted" }, "No schema");
|
|
153
153
|
default:
|
|
154
|
-
return /* @__PURE__ */ e.createElement("span", { className: "inputwrap" }, /* @__PURE__ */ e.createElement("input", { value:
|
|
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
155
|
}
|
|
156
156
|
}
|
|
157
|
-
function
|
|
157
|
+
function ne({ optKey: t, settings: s, setSettings: n, disabled: i, onOptionOpen: c, customWidgets: r }) {
|
|
158
158
|
if (t.startsWith("<")) return /* @__PURE__ */ e.createElement("div", { className: "row custom" }, /* @__PURE__ */ e.createElement("span", { className: "muted" }, "⚙ Custom widget ", t));
|
|
159
|
-
const o =
|
|
160
|
-
const
|
|
161
|
-
return delete
|
|
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
162
|
}), d = (o == null ? void 0 : o.label) || (o == null ? void 0 : o.full_label) || t;
|
|
163
|
-
return /* @__PURE__ */ e.createElement("div", { className: "row" + (
|
|
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
164
|
}
|
|
165
|
-
function
|
|
166
|
-
const [o, p] =
|
|
167
|
-
var
|
|
168
|
-
return
|
|
169
|
-
}).slice(0, 60).map(([
|
|
170
|
-
return /* @__PURE__ */ e.createElement(
|
|
171
|
-
p(
|
|
172
|
-
} },
|
|
173
|
-
const
|
|
174
|
-
return
|
|
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
175
|
})) : /* @__PURE__ */ e.createElement("p", { className: "muted" }, "No builder"))));
|
|
176
176
|
}
|
|
177
177
|
export {
|
|
178
|
-
|
|
178
|
+
ce as default
|
|
179
179
|
};
|