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.
- package/CHANGELOG.md +134 -0
- package/README.md +13 -8
- package/THIRD-PARTY-NOTICES.md +7 -2
- package/components/dist/SettingsPanel.js +12 -198
- package/data/config-schema-lean.json +1 -0
- package/data/processes.js +1 -1
- package/data/ui-tree-keys.json +1 -0
- package/engine/src/client.js +9 -0
- package/engine/src/data.js +6 -0
- package/engine/src/settings.js +12 -627
- package/engine/src/sla_core.js +14 -8
- package/engine/src/toggle_eval.js +9 -77
- package/package.json +24 -17
- package/types/client.d.ts +3 -0
- package/types/components.d.ts +3 -29
- package/types/settings.d.ts +15 -0
- package/types/toggle.d.ts +2 -22
- package/types/viewer-gcode.d.ts +2 -29
- package/types/viewer-loaders.d.ts +1 -33
- package/types/viewer-toolpath.d.ts +3 -113
- package/types/viewer.d.ts +6 -213
- package/viewer/dist/Viewport.js +9 -6571
- package/viewer/dist/gcode_reexport.js +1 -0
- package/viewer/dist/loaders_reexport.js +1 -0
- package/viewer/dist/toolpath_reexport.js +1 -0
- package/components/README.md +0 -44
- package/components/styles.css +0 -88
- package/engine/src/kernel_setting_keys.js +0 -132
- package/engine/src/preset_file.js +0 -99
- package/types/.omc/state/sessions/02e5ddf5-4378-46e1-8fca-e90412c30804/pre-tool-advisory-throttle.json +0 -10
- package/viewer/README.md +0 -423
- package/viewer/dist/gcode_parse.js +0 -190
- package/viewer/dist/make_worker.js +0 -31
- package/viewer/dist/model_loaders.js +0 -184
- package/viewer/dist/parse_3mf-C2X_W442.js +0 -300
- package/viewer/dist/parse_3mf.worker.js +0 -10
- package/viewer/dist/sl1_encode.worker.js +0 -10
- package/viewer/dist/sl1_write-CtX8cVL7.js +0 -302
- package/viewer/dist/sla_reconstruct-CXkYhux2.js +0 -176
- package/viewer/dist/sla_reconstruct.worker.js +0 -24
- package/viewer/dist/sla_slice.worker.js +0 -74
- package/viewer/dist/toolpath_gpu-4foc4FaU.js +0 -482
- package/viewer/dist/toolpath_gpu.js +0 -13
- 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
|
-

|
|
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/
|
|
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/
|
|
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/
|
|
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` +
|
|
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/
|
|
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/
|
|
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/
|
|
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.
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -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`
|
|
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
|
-
##
|
|
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
|
|
2
|
-
import
|
|
3
|
-
import { uiTree as
|
|
4
|
-
import {
|
|
5
|
-
import { disabledKeys as
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
+
l as default,
|
|
12
|
+
d as schema,
|
|
13
|
+
f as toggle,
|
|
14
|
+
h as uiTree
|
|
201
15
|
};
|