three-slicer 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +253 -21
- package/data/filaments.js +1 -1
- package/data/preset-keys.json +1 -0
- package/data/processes.js +1 -1
- package/engine/README.md +259 -3
- package/engine/index.d.ts +74 -5
- package/engine/index.js +9 -4
- package/engine/src/client.js +136 -0
- package/engine/src/data.js +3 -0
- package/engine/src/preset_file.js +99 -0
- package/engine/src/settings.js +75 -0
- package/engine/src/slicer.worker.js +13 -3
- package/package.json +20 -10
- package/types/client.d.ts +74 -0
- package/types/components.d.ts +10 -0
- package/types/data/preset-keys.json.d.ts +20 -0
- package/types/data/printers.json.d.ts +2 -2
- package/types/data.d.ts +4 -0
- package/types/settings.d.ts +82 -1
- package/types/viewer.d.ts +57 -1
- package/types/worker.d.ts +87 -0
- package/viewer/README.md +156 -2
- package/viewer/dist/Viewport.js +2937 -2757
- package/viewer/styles.css +5 -0
- package/types/gen_settings_types.mjs +0 -70
package/types/worker.d.ts
CHANGED
|
@@ -1,3 +1,90 @@
|
|
|
1
1
|
// three-slicer/worker — the slice worker script. Not a module you import, but the target of
|
|
2
2
|
// `new Worker(new URL('three-slicer/worker', import.meta.url), { type: 'module' })`.
|
|
3
|
+
//
|
|
4
|
+
// The protocol below is the raw message contract. `createSlicerClient()` from `three-slicer/client` wraps it in
|
|
5
|
+
// promises and is the easier way in; these types are here for hosts driving the worker directly, and because the
|
|
6
|
+
// shape has traps that only a written-down contract makes visible — see the slice request and `erase`.
|
|
7
|
+
import type { SliceResult } from '../engine/index.d.ts'
|
|
8
|
+
|
|
9
|
+
/** Brush geometry shared by paint and erase: the hit facet, the hit point, the camera, the radius. */
|
|
10
|
+
export interface BrushArgs {
|
|
11
|
+
facet: number
|
|
12
|
+
hx: number; hy: number; hz: number
|
|
13
|
+
cx: number; cy: number; cz: number
|
|
14
|
+
radius: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Painting state, following OrcaSlicer's own `EnforcerBlockerType`: one enum serving two jobs.
|
|
19
|
+
* `1` is both the support ENFORCER and Extruder 1, `2` is both the BLOCKER and Extruder 2, `3..16` are
|
|
20
|
+
* Extruders 3 to 16. `0` (NONE) is deliberately not a value here — see the `erase` command.
|
|
21
|
+
*/
|
|
22
|
+
export type PaintState = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16
|
|
23
|
+
|
|
24
|
+
/** Selection tools that are not the radius brush. All three take the mesh's own topology instead of a radius. */
|
|
25
|
+
export type PaintTool = 'smart' | 'bucket' | 'triangle'
|
|
26
|
+
|
|
27
|
+
export type SlicerRequest =
|
|
28
|
+
/** Load the kernel (and spawn the mt thread pool) ahead of the first slice. Reply: `warm`. */
|
|
29
|
+
| { cmd: 'warmup' }
|
|
30
|
+
/**
|
|
31
|
+
* Register a mesh for painting. `keepPaint` says this is the same model in a new place, so the marks carry
|
|
32
|
+
* over — the reply reports whether they actually did. Reply: `prepared`.
|
|
33
|
+
*/
|
|
34
|
+
| { cmd: 'prepare'; stl: ArrayBuffer; keepPaint?: boolean }
|
|
35
|
+
/**
|
|
36
|
+
* Paint. With `state` it addresses any extruder; with `enforcer` it stays on the original boolean pair, where
|
|
37
|
+
* `false` means BLOCKER. `states` asks for those states' counts back. Reply: `painted`.
|
|
38
|
+
*/
|
|
39
|
+
| (BrushArgs & { cmd: 'paint'; state?: PaintState; enforcer?: boolean; tool?: PaintTool
|
|
40
|
+
angle?: number; cursor?: 'sphere' | 'circle'; states?: PaintState[] })
|
|
41
|
+
/**
|
|
42
|
+
* Return the brushed facets to the default extruder. Its OWN command rather than `{cmd:'paint', state:0}`:
|
|
43
|
+
* embind coerces a JS `false` to the int `0` == NONE, and the legacy blocker brush sends exactly that `false`,
|
|
44
|
+
* so admitting `0` on the state path would let a stray boolean erase silently. Reply: `painted`.
|
|
45
|
+
*/
|
|
46
|
+
| (BrushArgs & { cmd: 'erase'; tool?: PaintTool; angle?: number; states?: PaintState[] })
|
|
47
|
+
/**
|
|
48
|
+
* Load painting out of a 3mf. REPLACES every mark (the kernel's deserialize resets first), so it belongs
|
|
49
|
+
* immediately after a `prepare` and nowhere else. Reply: `painted`, with `applied`.
|
|
50
|
+
*/
|
|
51
|
+
| { cmd: 'importPaint'; facets: Int32Array | number[]; hex: string; states?: PaintState[] }
|
|
52
|
+
/** Every marked facet's split tree, in the CURRENT selector's numbering. Reply: `paintExport`. */
|
|
53
|
+
| { cmd: 'exportPaint' }
|
|
54
|
+
/** Wipe every state at once. Reply: `painted`, all zero. */
|
|
55
|
+
| { cmd: 'clear'; states?: PaintState[] }
|
|
56
|
+
/** The painted overlay triangles. `enf`/`blk` always; `states` adds those states. Reply: `overlay`. */
|
|
57
|
+
| { cmd: 'overlay'; states?: PaintState[] }
|
|
58
|
+
/**
|
|
59
|
+
* Slice — and note it carries **no `cmd`**: slicing is the worker's default action, and leaving the field off
|
|
60
|
+
* is what selects it. `params` must be a JSON **string**; the worker hands it straight to the kernel, which
|
|
61
|
+
* parses JSON text. (`createSlicer().slice()` on the direct handle also accepts an object and stringifies it.)
|
|
62
|
+
* Replies: `progress` and `layer` while it runs, then `done` — or `error`.
|
|
63
|
+
*/
|
|
64
|
+
| { stl: ArrayBuffer; params: string }
|
|
65
|
+
|
|
66
|
+
export type SlicerResponse =
|
|
67
|
+
| { type: 'warm' }
|
|
68
|
+
| { type: 'prepared'; facets: number; kept: boolean }
|
|
69
|
+
/**
|
|
70
|
+
* `enf`/`blk` are the counts for states 1 and 2 and are always present, so a listener predating the
|
|
71
|
+
* state-addressed protocol keeps working. `counts` appears only when the request named `states`.
|
|
72
|
+
*/
|
|
73
|
+
| { type: 'painted'; enf: number; blk: number; counts?: Record<number, number>; applied?: number }
|
|
74
|
+
/** `supported: false` from a kernel built before the export binding existed — it does not throw. */
|
|
75
|
+
| { type: 'paintExport'; supported: boolean; facets: number[]; hex: string }
|
|
76
|
+
| { type: 'overlay'; enf: Float32Array; blk: Float32Array; overlays?: Record<number, Float32Array> }
|
|
77
|
+
/**
|
|
78
|
+
* Sent once, unprompted, by the multithreaded kernel only: the address of the support-progress counter and of
|
|
79
|
+
* the cancel flag, inside a SharedArrayBuffer. Writing 1 to the cancel flag stops a slice the worker is
|
|
80
|
+
* currently blocked inside. A single-threaded kernel has no shared buffer and never sends this.
|
|
81
|
+
*/
|
|
82
|
+
| { type: 'supsab'; buf: SharedArrayBuffer; ptr: number; cancelPtr: number }
|
|
83
|
+
| { type: 'progress'; done: number; total: number }
|
|
84
|
+
/** One sliced layer, its typed arrays transferred rather than copied. */
|
|
85
|
+
| { type: 'layer'; z: number; idx: number; gcode: string; paths: Float32Array; widths: Float32Array }
|
|
86
|
+
/** `result.stats.streamed` says whether the G-code came through `layer` messages or is on the result. */
|
|
87
|
+
| { type: 'done'; result: SliceResult }
|
|
88
|
+
| { type: 'error'; error: string }
|
|
89
|
+
|
|
3
90
|
export {}
|
package/viewer/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm i three-slicer
|
|
8
|
+
npm i three-slicer react react-dom three # the three are optional peers; the viewer needs all of them
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
```jsx
|
|
@@ -14,12 +14,26 @@ import Viewport from 'three-slicer/viewer'
|
|
|
14
14
|
|
|
15
15
|
function App() {
|
|
16
16
|
const [settings, setSettings] = useState({}) // OrcaSlicer schema keys; sparse — defaults fill the rest
|
|
17
|
-
|
|
17
|
+
// The container matters — see Layout below.
|
|
18
|
+
return (
|
|
19
|
+
<div style={{ position: 'relative', width: '100%', height: '100vh' }}>
|
|
20
|
+
<Viewport settings={settings} setSettings={setSettings} />
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
18
23
|
}
|
|
19
24
|
```
|
|
20
25
|
|
|
21
26
|
Styles are injected into the component's Shadow DOM root — nothing to import, and host CSS cannot collide.
|
|
22
27
|
|
|
28
|
+
## Layout
|
|
29
|
+
|
|
30
|
+
**Give it a positioned, sized container.** The shadow host is `display: contents`, so it contributes no box of its
|
|
31
|
+
own, and the shell inside is `position: absolute; inset: 0`. It therefore fills the nearest *positioned* ancestor —
|
|
32
|
+
`position: relative` (or absolute/fixed) with a real height. Drop it into a plain static `<div>` and it escapes to
|
|
33
|
+
the initial containing block instead, which usually looks like the viewer covering the whole page.
|
|
34
|
+
|
|
35
|
+
There is no width or height prop: the component always fills that container, and resizes with it.
|
|
36
|
+
|
|
23
37
|
Slice parameters are derived from `settings` via `three-slicer`'s schema mapping — pair it with `three-slicer/components`' `<SettingsPanel/>` sharing the same state for a full slicer UI.
|
|
24
38
|
|
|
25
39
|
A `.3mf` written by a slicer (OrcaSlicer/BambuStudio save, MakerWorld download) imports as a **project**: plate layout, project settings, and support/material painting are restored, not just the meshes. Where a facet carries both paint kinds, material paint wins and the dropped support paint is reported.
|
|
@@ -63,6 +77,55 @@ Every panel can be switched off, and the values the component owns can be seeded
|
|
|
63
77
|
second while slicing.
|
|
64
78
|
- **`onSliced`** — `{plate, stats, gcode}` when a slice is cached. Switching plate tabs does not re-fire it.
|
|
65
79
|
|
|
80
|
+
## Keyboard and mouse
|
|
81
|
+
|
|
82
|
+
Which keys are live depends on the tab, and every one is ignored while an input has focus. Letter shortcuts match
|
|
83
|
+
the **physical** key, so they keep working under a non-Latin keyboard layout — with an IME on, the Korean layout
|
|
84
|
+
reports `'ㅁ'` for M, and matching on the character would silently kill every letter shortcut while leaving the
|
|
85
|
+
arrows and Delete working.
|
|
86
|
+
|
|
87
|
+
| Both tabs | |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `Ctrl/⌘ + R` | Slice the current plate |
|
|
90
|
+
| `Ctrl/⌘ + C` / `V` / `X` | Copy · paste · cut the selection |
|
|
91
|
+
| `Ctrl/⌘ + K` or `Ctrl/⌘ + D` | Duplicate |
|
|
92
|
+
| `Ctrl/⌘ + A` | Select all |
|
|
93
|
+
| `Ctrl/⌘ + Z` | Undo — bound to the component root, so it never reaches your app's own Ctrl+Z |
|
|
94
|
+
| `Ctrl/⌘ + Shift + Z` or `Ctrl/⌘ + Y` | Redo |
|
|
95
|
+
|
|
96
|
+
| Prepare | |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| `M` or `G` · `R` · `S` | Move · rotate · scale gizmo |
|
|
99
|
+
| `Arrow keys` | Nudge 10mm — `Shift` for 1mm |
|
|
100
|
+
| `PageUp` / `PageDown` | Rotate ±45° |
|
|
101
|
+
| `Delete` / `Backspace` | Remove the selection |
|
|
102
|
+
| `Z` · `B` | Zoom to all · zoom to bed |
|
|
103
|
+
| `Esc` | Cancel the current tool |
|
|
104
|
+
| `?` | Shortcut help overlay |
|
|
105
|
+
|
|
106
|
+
| Preview | |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `↑` / `↓` | Step one layer — `Shift` for ten |
|
|
109
|
+
| `L` · `T` | Single-layer view · travel moves |
|
|
110
|
+
| `Z` · `B` | Zoom to all · zoom to bed |
|
|
111
|
+
| `Esc` | Back to Prepare |
|
|
112
|
+
|
|
113
|
+
Mouse selection follows upstream's rules: a plain click **replaces** the selection, `Ctrl/⌘ + click` adds or
|
|
114
|
+
removes, a plain click on something already selected **keeps** the set (which is what makes dragging several
|
|
115
|
+
objects work), `Shift + drag` is a box select, and clicking empty space clears. Alt is not a de-select, in
|
|
116
|
+
upstream or here.
|
|
117
|
+
|
|
118
|
+
## Undo and redo
|
|
119
|
+
|
|
120
|
+
The history covers the viewport's **own** state: object transforms, additions, removals, per-object extruder and
|
|
121
|
+
visibility. Fifty steps, with repeats of the same action inside half a second folded into one — so a held arrow key
|
|
122
|
+
undoes as a single move rather than forty.
|
|
123
|
+
|
|
124
|
+
What it deliberately does not cover, and why the boundary sits there: `settings` is **yours**, passed in as a prop,
|
|
125
|
+
so nothing inside the component can put it back. Painting is outside it too (restoring a step would need a kernel
|
|
126
|
+
re-`prepare` for every entry), as are the prime tower and the plate count, which write host settings. If you want
|
|
127
|
+
those undoable, undo them on your side of the prop.
|
|
128
|
+
|
|
66
129
|
## Materials and painting
|
|
67
130
|
|
|
68
131
|
The filament card holds **one material per extruder**: add an extruder, pick its colour, and pick its filament preset from the vendor catalog (`filamentPresets()` in `three-slicer/settings` — the shortlist is the printer model's own recommendation, the full list everything compatible). Selecting a row aims the material picker and the settings form at that extruder; the colours feed the object meshes, the prime tower and the preview's **Filament** view type, which colours every segment by the extruder that printed it.
|
|
@@ -76,6 +139,97 @@ Two limits to expect while using it:
|
|
|
76
139
|
|
|
77
140
|
If you consume `three-slicer/viewer/toolpath` directly, note that the stride-8 role field `paths[k+3]` now carries the printing extruder alongside the role as `role + tool * 16`. Mask it — `& 15` for the role, `>>> 4` for the tool. Streams produced before this encoding are entirely below 16 and decode to their own role with tool 0, so older data still reads correctly.
|
|
78
141
|
|
|
142
|
+
## Turning behaviour off
|
|
143
|
+
|
|
144
|
+
`panels` controls what is drawn; `features` controls what the component *does* outside its own box. Same rule —
|
|
145
|
+
opt-out only, everything on unless you say `false`, an unknown key stays on.
|
|
146
|
+
|
|
147
|
+
```jsx
|
|
148
|
+
<Viewport
|
|
149
|
+
gcode={text}
|
|
150
|
+
panels={{ sidebar: false, topBar: false }}
|
|
151
|
+
features={{ warmup: false, shortcuts: false, logs: false }}
|
|
152
|
+
/>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Key | Off means |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `shortcuts` | No keyboard bindings. They are installed on `window`, so while the viewer is mounted they fire wherever focus is (inputs excepted) — and `Ctrl+C` preventDefaults, which takes a host page's own copy. Turn them off when your app has its own. |
|
|
158
|
+
| `warmup` | The WASM kernel is not loaded on mount. Nothing is downloaded or compiled until something actually slices — which for a `gcode`-only viewer is never, so this is several megabytes and a thread pool you stop paying for. |
|
|
159
|
+
| `drop` | No drag and drop onto the canvas. |
|
|
160
|
+
| `filePicker` | The file dialog never opens, from any of the buttons that open it. |
|
|
161
|
+
| `contextMenu` | No right-click menu. The browser's own menu stays suppressed over the canvas either way — OrbitControls preventDefaults `contextmenu` itself. |
|
|
162
|
+
| `logs` | No console output, from the component or from the slice worker. |
|
|
163
|
+
|
|
164
|
+
### Read-only panels
|
|
165
|
+
|
|
166
|
+
A sidebar panel can also be shown but not editable — the shape you want when the host picks the printer, process
|
|
167
|
+
and filament itself and does not want the user changing them. It is a third value on the same `panels` key:
|
|
168
|
+
|
|
169
|
+
```jsx
|
|
170
|
+
<Viewport
|
|
171
|
+
settings={presetSettings}
|
|
172
|
+
panels={{ printerCard: 'readonly', filamentCard: 'readonly', processCard: 'readonly' }}
|
|
173
|
+
/>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Lockable: `sidebar`, `printerCard`, `filamentCard`, `objectList`, `towerCard`, `previewControls`, `processCard`,
|
|
177
|
+
`sliceBar`. `sidebar: 'readonly'` locks the whole column at once; the per-card keys are for mixing, such as a
|
|
178
|
+
locked printer card above a live object list. Every other panel takes `true`/`false` only — a `'readonly'` that
|
|
179
|
+
silently did nothing on half the keys would be worse than not offering it.
|
|
180
|
+
|
|
181
|
+
The panel is drawn normally and keeps showing real values; it just cannot be clicked, typed into or tabbed to, and
|
|
182
|
+
that includes any node you passed into it (`motionPanel`, `filamentPanel`, `processPanel`). Implemented with the
|
|
183
|
+
`inert` attribute, so it needs Chrome 102+, Safari 15.5+ or Firefox 112+.
|
|
184
|
+
|
|
185
|
+
**It locks the UI path, not the value.** `settings` is still your prop, and other paths inside the viewer still
|
|
186
|
+
write it — importing a 3mf project applies that project's settings whether or not the cards are locked.
|
|
187
|
+
|
|
188
|
+
### Printer preset files
|
|
189
|
+
|
|
190
|
+
The printer card carries **Load** and **Save**. Save writes an OrcaSlicer preset: a machine `.json` on its own, or
|
|
191
|
+
an `.orca_printer` bundle when the settings map also names a process or filament preset (the cards record those as
|
|
192
|
+
`printer_settings_id` / `print_settings_id` / `filament_settings_id`, so nothing extra has to be tracked). Load
|
|
193
|
+
accepts the same set upstream's dialog does — `.json`, `.orca_printer`, `.orca_bundle`, `.orca_filament`, `.zip` —
|
|
194
|
+
and applies every preset it finds, clearing that type's keys first.
|
|
195
|
+
|
|
196
|
+
`inherits` is followed through the shipped printer catalog, so a vendor file loads with its bed intact; when the
|
|
197
|
+
parent is unknown the file's own values are still applied and the notice says so. Codec details and the reason
|
|
198
|
+
that matters are in the [package README](../README.md#preset-files).
|
|
199
|
+
|
|
200
|
+
Load opens a file dialog, so it respects `features.filePicker`; Save goes through `onExport` like every other
|
|
201
|
+
download.
|
|
202
|
+
|
|
203
|
+
### Taking the saves
|
|
204
|
+
|
|
205
|
+
The save buttons are inside the component, so `onExport` is the only way to send a file somewhere other than the
|
|
206
|
+
browser's download folder. It sees the 3mf project, the STL and each plate's G-code. Return truthy to say you
|
|
207
|
+
handled it:
|
|
208
|
+
|
|
209
|
+
```jsx
|
|
210
|
+
<Viewport onExport={(file, filename) => { uploadToServer(file, filename); return true }} />
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Return nothing and the download happens as well, which is useful for logging what was saved without changing it.
|
|
214
|
+
|
|
215
|
+
## What the host cannot drive
|
|
216
|
+
|
|
217
|
+
The component owns its scene, and the props are the whole interface — there is no ref, no imperative handle, and no
|
|
218
|
+
`models` prop. Worth knowing before you design around it:
|
|
219
|
+
|
|
220
|
+
- **Models enter through the UI only** — the file dialog or drag and drop. A host cannot hand a mesh in, and cannot
|
|
221
|
+
remove or transform one. What it *can* do is watch: the `objects` event reports `{id, name, extruder, visible}`
|
|
222
|
+
for every object as the set changes.
|
|
223
|
+
- **Slicing is triggered from the UI or by `defaultAutoSlice`**, which unlike the in-app toggle also performs the
|
|
224
|
+
first slice — that is what makes a panel-less embed able to slice at all. The result arrives on `onSliced`.
|
|
225
|
+
- **`gcode` is one-way**: pass G-code text and it is drawn on the selected plate instead of a slice result, and
|
|
226
|
+
auto re-slice leaves that plate alone while it is set.
|
|
227
|
+
- Everything else the component owns — camera, selection, plate count, paint state — is reported through `onEvent`
|
|
228
|
+
and settable only by the user.
|
|
229
|
+
|
|
230
|
+
If you need programmatic control over the scene, build on the subpath exports (`three-slicer/viewer/toolpath`,
|
|
231
|
+
`/loaders`, `/gcode`) and `three-slicer/client` rather than on `<Viewport/>`.
|
|
232
|
+
|
|
79
233
|
## Multithreaded slicing (automatic)
|
|
80
234
|
|
|
81
235
|
The engine ships two WASM kernels: single-threaded (default) and `-pthread` multithreaded (~2× wall-clock on layer-parallel wall generation, measured 12× on the wall pass itself). The worker picks the mt kernel automatically when the page is [cross-origin isolated](https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated) — i.e. served with:
|