three-slicer 0.2.1 → 0.2.3

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/viewer/README.md CHANGED
@@ -38,9 +38,10 @@ Slice parameters are derived from `settings` via `three-slicer`'s schema mapping
38
38
 
39
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.
40
40
 
41
- Props: `settings`, `setSettings`, and three optional React-node slots rendered in the right sidebar —
42
- `processPanel` (the process card), `motionPanel` (folded into the printer card) and `filamentPanel`
43
- (folded into the filament card, next to the material picker).
41
+ Props: `settings`, `setSettings`, and three optional sidebar slots — `processPanel` (the process card) and
42
+ `motionPanel` (folded into the printer card) take a React node; `filamentPanel` (folded into the filament card,
43
+ next to the material picker) takes a **function** `(settings, setSettings) => ReactNode`, because the filament
44
+ card binds each extruder's per-extruder values.
44
45
 
45
46
  Subpath exports for custom UIs (framework-free, no React):
46
47
  - `three-slicer/viewer/toolpath` — GPU toolpath renderer (`buildSegmentData`, `makeToolpath`, view-type colorers)
@@ -63,25 +64,88 @@ Every panel can be switched off, and the values the component owns can be seeded
63
64
  - **`panels`** — `{name: false}` hides one. Everything is visible by default, so a host only ever opts out and a
64
65
  panel added in a later version does not vanish for hosts that listed the ones they wanted. `sidebar: false` drops
65
66
  the whole right column; the rest are `topBar`, `gizmoRail`, `objectToolbar`, `paintPanel`, `statsCard`, `plateBar`,
66
- `emptyHint`, `status`, `printerCard`, `filamentCard`, `resinCard`, `objectList`, `previewControls`, `processCard`,
67
- `sliceBar`. Which of `filamentCard`/`resinCard` renders follows the printer profile's technology: a profile whose
67
+ `emptyHint`, `status`, `printerCard`, `filamentCard`, `resinCard`, `towerCard`, `objectList`, `previewControls`,
68
+ `processCard`, `sliceBar`, `moveBar`, `bedWarn` (the off-bed warning strip). Which of `filamentCard`/`resinCard` renders follows the printer profile's technology: a profile whose
68
69
  `printer_technology` says SLA swaps the filament card (and the prime tower, and the painting brushes) for the
69
70
  resin card, and slicing routes to the kernel's `slice_sla` — PrusaSlicer's ported support/pad chain — with an
70
71
  `.sl1` export instead of G-code (portrait masks, like every SL1-family machine). The preview lifts the model by
71
72
  `stats.lift_layers` (pad + elevation), and a request the port does not cover (hollowing, organic trees)
72
73
  surfaces as a typed error instead of a wrong print.
74
+ An `.sl1` can also be OPENED (file picker or drag & drop). This viewer's own exports carry a SCENE sidecar
75
+ (`Metadata/threeslicer_scene.bin`: the model STL plus the support and pad meshes the preview was showing, ~3%
76
+ of the archive), so reopening one shows the exported surface ITSELF — no reconstruction, nothing approximated,
77
+ and the whole import lands in ~0.3s. A foreign SL1 has no such record, and there its SHAPE is reconstructed
78
+ from the masks —
79
+ every layer streamed through surface nets in workers (six producers turning masks into occupancy slices, one
80
+ sequential consumer sweeping them). It runs in TWO passes: a quarter-resolution one lands a real shaded mesh
81
+ in well under a second, then the full one replaces it (measured 0.7s and 4.2s on a 1095-layer archive), at the archive's own layer height in z and ~0.12mm
82
+ in xy, with the anti-aliased mask grays interpolated for sub-voxel edges and both the surface and its normals
83
+ relaxed (the layer ripple a mask grid leaves behind is largely a shading artefact) — and shown as a solid mesh on the
84
+ selected plate, with the layer slider section-cutting it like any sliced result. While the reconstruction
85
+ runs a translucent stack of sampled masks stands in, until the coarse pass replaces it.
86
+ Colours: a mask records what cures, not which pixels were model, support or pad — so this viewer's own
87
+ exports carry a role sidecar (`Metadata/threeslicer_roles.bin`, the support/pad segments per layer; every
88
+ other consumer ignores the unknown member) and reconstruct in the sliced preview's three colours. A foreign
89
+ SL1 has no such record and reconstructs in one colour. Opening one applies what the archive states —
90
+ `printer_technology` first of all, then the exposure family, the layer height and the pixel grid read off a
91
+ mask header — so a file opened in a fresh FFF session switches the whole route to SLA instead of stranding
92
+ resin masks on a filament bed. The one number it cannot supply is the display's PHYSICAL size: millimetres
93
+ appear nowhere in an SL1, so the preview is scaled by the printer set in the viewer and the import notice says
94
+ so. Export hands an imported archive back byte-identical rather than re-rasterizing empty geometry.
73
95
  - **`gcode`** — G-code text drawn on the selected plate instead of a slice result. `parseGcode` recovers roles from
74
96
  `;TYPE:` (OrcaSlicer/PrusaSlicer/Cura), from `;_EXTRUSION_ROLE:` tags and from this kernel's own feature comments;
75
97
  bead width comes from `;WIDTH:` or, absent that, from E. What the file never states cannot be recovered: an
76
98
  unmarked run reads as wall, and there is no print-time estimate (that needs the machine's acceleration limits).
99
+ - **`sl1`** — the SLA half of that contract: an `.sl1` archive (`File`, raw bytes, or `{name, data}`) rendered as a
100
+ raster preview on the selected plate, again without starting the kernel. It routes into the same import the
101
+ picker and a drop use, so it behaves identically — including the two things `gcode` does not do: it writes the
102
+ archive's own job description into your `settings` (`printer_technology` among them, per the paragraph above),
103
+ and it reconstructs a surface from the masks in the background. Set both and the G-code wins; one plate holds one
104
+ artifact. It re-injects on the value's IDENTITY, so hold the File in state rather than rebuilding it each render.
105
+ - **The move scrub** (`panels.moveBar`) — the horizontal counterpart of the layer slider, under the canvas in
106
+ Preview: the vertical one picks WHICH layers are shown, this one walks inside the top one. It is upstream's
107
+ sequential view (`GCodeViewer`'s `update_sequential_view_current`) in this viewer's terms. On an FFF result it
108
+ cuts that layer at a move — extrusions and travels together, in the order the printer performs them, which the
109
+ two draw lists do not store and `moveCursor` recovers — and marks where the nozzle is: a solid cone at the
110
+ current position and a translucent one where the layer ends. Both are fixed SCREEN size and drawn over
111
+ everything, because a marker that scales with the scene is a speck zoomed out and a marker that respects depth
112
+ is buried in the bead it is pointing at. Position changes reach the host as the `moveScrub` event.
113
+ FFF only, and deliberately: mSLA cures a whole layer in one exposure, so a resin layer has no intra-layer
114
+ order to walk — the same conclusion upstream reaches, PrusaSlicer's SLA preview having a layer slider and no
115
+ horizontal one.
116
+ - **`files`** — content imported once on mount, through the same extension dispatch as a drop: models and 3mf
117
+ projects, `.sl1` archives, and preset files. Mount-only on purpose — a host that rebuilds the array each render
118
+ must not re-import its models; runtime loading stays with the picker and drop.
119
+ - **`sliceRequest`** — the host's Slice button: a token whose identity change requests one slice of the current
120
+ plate. The mount value is inert, so hold a counter in state and bump it. Ignored under the same conditions
121
+ the built-in slice bar is not pressable (empty scene, an injected `gcode`/`sl1` plate); a change during a
122
+ running slice cancels and re-slices, so the last request wins.
77
123
  - **`defaultExtruderColors`**, **`defaultAutoSlice`** — initial values for state the component owns. Unlike the
78
124
  in-app toggle, `defaultAutoSlice` also performs the *first* slice, which is what makes a panel-less embed able to
79
125
  slice at all.
80
126
  - **`onEvent`** — one channel for every change: `canvasMode`, `objects`, `selectedPlate`, `plateCount`,
81
- `extruderColors`, `autoSlice`, `slicing`, `progress`, `viewType`, `paintMode`, `layerCount`, `layerRange`,
82
- `error`, `notice`. Initial values are not announced — the host passed them. `progress` fires several times a
83
- second while slicing.
84
- - **`onSliced`** — `{plate, stats, gcode}` when a slice is cached. Switching plate tabs does not re-fire it.
127
+ `extruderColors`, `autoSlice`, `slicing`, `progress`, `sliceRate`, `viewType`, `paintMode`, `layerCount`,
128
+ `layerRange`, `moveScrub`, `error`, `notice`. Initial values are not announced — the host passed them.
129
+ `progress` and `sliceRate` fire several times a second while slicing.
130
+ - **`onSliced`** — `{plate, stats, gcode, throughput}` when a slice is cached. Switching plate tabs does not
131
+ re-fire it. `throughput` sits beside `stats` rather than inside it, because that is where the engine puts it:
132
+ it is measured around the call, not reported by the kernel.
133
+
134
+ `sliceRate` is live throughput — layers finished per second over a 250ms window, `0` between slices, and shown in
135
+ the slice bar as well. When it appears depends on the technology, because the two publish per-layer news at
136
+ opposite ends of the run:
137
+
138
+ - **SLA** reports it throughout: its progress counter is linear in layers. (Its layer *stream* is not usable for
139
+ this — `slice_sla` builds the whole scene and then drains the sink, measured as 1095 layer messages inside a
140
+ 22ms burst at the end of a 2.8s run, which would time the drain rather than the slicing.)
141
+ - **FFF** reports it once the emission pass starts streaming layers. PASS1 publishes per-mille of itself rather
142
+ than a layer count (the total only arrives when it ends), and surfaces and support publish nothing per layer,
143
+ so there is nothing to measure before that and nothing is invented. Measured on a 38MB model: a 4.0s slice,
144
+ 2.8s of it PASS1, the rate shown for the final 0.6s. On a support-heavy model emission is a much larger share.
145
+
146
+ The whole-slice figure is `throughput` on `onSliced`, and it is present on both technologies — wall milliseconds,
147
+ the kernel's own share, layers/second, and ms per million segments. It reads lower than the live figure whenever
148
+ the live one covers only part of the run.
85
149
 
86
150
  ## Keyboard and mouse
87
151
 
@@ -209,8 +273,8 @@ download.
209
273
  ### Taking the saves
210
274
 
211
275
  The save buttons are inside the component, so `onExport` is the only way to send a file somewhere other than the
212
- browser's download folder. It sees the 3mf project, the STL and each plate's G-code. Return truthy to say you
213
- handled it:
276
+ browser's download folder. It sees every save: the 3mf project, the STL, each plate's G-code, an SLA plate's
277
+ `.sl1` archive, and preset files. Return truthy to say you handled it:
214
278
 
215
279
  ```jsx
216
280
  <Viewport onExport={(file, filename) => { uploadToServer(file, filename); return true }} />
@@ -223,11 +287,23 @@ Return nothing and the download happens as well, which is useful for logging wha
223
287
  The component owns its scene, and the props are the whole interface — there is no ref, no imperative handle, and no
224
288
  `models` prop. Worth knowing before you design around it:
225
289
 
226
- - **Models enter through the UI only** — the file dialog or drag and drop. A host cannot hand a mesh in, and cannot
227
- remove or transform one. What it *can* do is watch: the `objects` event reports `{id, name, extruder, visible}`
228
- for every object as the set changes.
229
- - **Slicing is triggered from the UI or by `defaultAutoSlice`**, which unlike the in-app toggle also performs the
230
- first slice — that is what makes a panel-less embed able to slice at all. The result arrives on `onSliced`.
290
+ - **Models enter through the UI, or once at mount through `files`** — after mount, runtime loading stays with the
291
+ file dialog and drag and drop. A host cannot remove or transform an object, or hand a mesh in mid-session. What
292
+ it *can* do is watch: the `objects` event reports `{id, name, extruder, visible}` for every object as the set
293
+ changes.
294
+ - **Slicing is triggered from the UI, by `defaultAutoSlice`, or by bumping `sliceRequest`** — a token prop whose
295
+ identity change requests one slice of the current plate (the mount value is inert), which is what a host with
296
+ the built-in chrome hidden wires its own Slice button to. The result arrives on `onSliced`.
297
+ - **The prime tower stand-in appears only when the plate actually changes tools** — objects assigned to more
298
+ than one extruder, or material paint reaching extruder 2 or higher. Loading a second filament is not enough:
299
+ with everything on T1 the slice emits no tool change and builds no tower, so drawing the box would promise one.
300
+ - **A settings change discards every slice result.** Everything a slice puts on screen — the toolpaths, the stats
301
+ card, the layer slider, the G-code export — describes the settings it ran with, and none of it says so. So when
302
+ a value in `settings` changes, the results are dropped and the viewer returns to Prepare; with `autoSlice` on
303
+ the re-slice then fills the empty preview. Two exceptions, neither of which claims to be a slice of these
304
+ settings: a plate injected through `gcode` / `sl1`, and an opened `.sl1` archive (that import applies the
305
+ archive's own settings, so invalidating on it would erase what the file was opened to show). Comparison is by
306
+ value, so a host that rebuilds its settings object every render does not lose its preview to it.
231
307
  - **`gcode` is one-way**: pass G-code text and it is drawn on the selected plate instead of a slice result, and
232
308
  auto re-slice leaves that plate alone while it is set.
233
309
  - Everything else the component owns — camera, selection, plate count, paint state — is reported through `onEvent`