three-slicer 0.2.2 → 0.2.4
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 +97 -0
- package/README.md +143 -19
- package/components/README.md +6 -3
- package/data/README.md +10 -9
- package/engine/PARAMS.md +248 -0
- package/engine/README.md +18 -264
- package/engine/examples/headless.mjs +4 -0
- package/engine/index.d.ts +26 -0
- package/engine/index.js +24 -4
- package/engine/src/client.js +17 -2
- package/engine/src/kernel_setting_keys.js +132 -0
- package/engine/src/settings.js +36 -2
- package/engine/src/slicer.worker.js +70 -6
- package/engine/src/slicer_core.js +0 -0
- package/engine/src/slicer_core.mt.js +0 -0
- package/engine/src/throughput.js +53 -0
- package/engine/src/warnings.js +23 -0
- package/package.json +1 -1
- package/types/.omc/state/sessions/02e5ddf5-4378-46e1-8fca-e90412c30804/pre-tool-advisory-throttle.json +10 -0
- package/types/settings.d.ts +20 -0
- package/types/viewer.d.ts +50 -1
- package/types/worker.d.ts +32 -3
- package/viewer/README.md +112 -16
- package/viewer/dist/Viewport.js +3772 -3295
- package/viewer/styles.css +18 -1
package/types/viewer.d.ts
CHANGED
|
@@ -52,6 +52,37 @@ export interface ViewportProps {
|
|
|
52
52
|
* shown on the selected plate; the kernel is never started. While it is set, auto re-slice leaves that plate alone.
|
|
53
53
|
*/
|
|
54
54
|
gcode?: string | null
|
|
55
|
+
/**
|
|
56
|
+
* An `.sl1` archive rendered as a raster preview — the SLA half of {@link ViewportProps.gcode}'s contract: it
|
|
57
|
+
* lands on the selected plate and the kernel is never started. Set both and the G-code wins; one plate holds
|
|
58
|
+
* one artifact.
|
|
59
|
+
*
|
|
60
|
+
* Two things it does that `gcode` does not. It writes to your `settings` — the archive's own job description,
|
|
61
|
+
* `printer_technology` included, because an .sl1 opened in an FFF session has to switch it. And it reconstructs
|
|
62
|
+
* a surface from the masks in the background, so the preview fills in a moment after the layers appear.
|
|
63
|
+
*
|
|
64
|
+
* Re-injects whenever the value's IDENTITY changes, so hold the File/buffer in state rather than building it
|
|
65
|
+
* during render — a fresh `new Uint8Array(...)` each render re-imports each render. `{name, data}` exists
|
|
66
|
+
* because the name is the import notice's subject and the filename a re-export hands back.
|
|
67
|
+
*/
|
|
68
|
+
sl1?: File | Blob | ArrayBuffer | Uint8Array | { name?: string, data: ArrayBuffer | Uint8Array } | null
|
|
69
|
+
/**
|
|
70
|
+
* Content imported once on mount, through the same extension dispatch as a drop: models (STL/OBJ/3MF/AMF/PLY —
|
|
71
|
+
* a 3mf project applies its settings, plates and painting), `.sl1` raster archives, and preset files
|
|
72
|
+
* (`.json` / `.orca_printer` / `.orca_bundle` / `.orca_filament` / `.zip`). The `{name, data}` form exists
|
|
73
|
+
* because the dispatch runs on the file NAME's extension. Deliberately mount-only — recreating the array on a
|
|
74
|
+
* later render does not re-import; runtime loading is the picker, drop, or a remount.
|
|
75
|
+
*/
|
|
76
|
+
files?: Array<File | { name: string, data: ArrayBuffer | Uint8Array }>
|
|
77
|
+
/**
|
|
78
|
+
* A token whose identity CHANGE requests one slice of the current plate — the host's Slice button:
|
|
79
|
+
* keep a counter in state and bump it. The mount value is inert (0 and null both slice nothing), so is
|
|
80
|
+
* setting it back to `null`. Ignored while a `gcode`/`sl1` injection holds the plate or the scene is
|
|
81
|
+
* empty — the same conditions under which the built-in slice bar is not pressable. A change while a
|
|
82
|
+
* slice is running cancels it and re-slices: the last request wins.
|
|
83
|
+
*/
|
|
84
|
+
sliceRequest?: number | string | object | null
|
|
85
|
+
|
|
55
86
|
/** Initial filament colours (hex), one per extruder. Defaults to the built-in T1/T2 pair. */
|
|
56
87
|
defaultExtruderColors?: string[] | null
|
|
57
88
|
/**
|
|
@@ -62,7 +93,12 @@ export interface ViewportProps {
|
|
|
62
93
|
/** Every value change in one channel. See {@link ViewportEvent}. */
|
|
63
94
|
onEvent?: (event: ViewportEvent) => void
|
|
64
95
|
/** A finished slice, fired where the result is cached — switching plate tabs does not re-fire it. */
|
|
65
|
-
|
|
96
|
+
/** `throughput` is how fast the slice ran — see `SliceThroughput` in the engine entry. Absent on a kernel
|
|
97
|
+
* result that carried none (a cached plate re-announced from before the field existed). */
|
|
98
|
+
onSliced?: (result: {
|
|
99
|
+
plate: number; stats: Record<string, unknown>; gcode: string
|
|
100
|
+
throughput?: { ms: number; kernelMs: number | null; layersPerSecond: number; msPerMsegment: number | null }
|
|
101
|
+
}) => void
|
|
66
102
|
}
|
|
67
103
|
|
|
68
104
|
/**
|
|
@@ -97,6 +133,9 @@ export type LockablePanel =
|
|
|
97
133
|
export type ViewportPanel =
|
|
98
134
|
| 'topBar' | 'gizmoRail' | 'objectToolbar' | 'paintPanel' | 'statsCard' | 'plateBar' | 'emptyHint' | 'status'
|
|
99
135
|
| 'sidebar' | 'printerCard' | 'filamentCard' | 'objectList' | 'previewControls' | 'processCard' | 'sliceBar'
|
|
136
|
+
/** The horizontal move scrub under the canvas — how far into the top shown layer the print has got. Preview
|
|
137
|
+
* and FFF only: a resin layer is cured in one exposure and has no intra-layer order to walk. */
|
|
138
|
+
| 'moveBar'
|
|
100
139
|
/** The over-the-bed warning, shown in Prepare when something leaves the printable area. */
|
|
101
140
|
| 'bedWarn'
|
|
102
141
|
/** The prime tower card. Only rendered anyway once a second filament exists. */
|
|
@@ -115,10 +154,20 @@ export type ViewportEvent =
|
|
|
115
154
|
| { type: 'slicing'; value: boolean }
|
|
116
155
|
/** 0..1. Fires several times a second while slicing — throttle on the host side if that matters. */
|
|
117
156
|
| { type: 'progress'; value: number }
|
|
157
|
+
/**
|
|
158
|
+
* Live throughput: layers finished per second over a 250ms window, `0` between slices. An SLA slice reports it
|
|
159
|
+
* throughout. An FFF slice reports it only once the emission pass begins streaming layers — the earlier passes
|
|
160
|
+
* publish no per-layer progress, so nothing is measured there and none is invented. For the whole-slice figure
|
|
161
|
+
* use `throughput` from `onSliced`.
|
|
162
|
+
*/
|
|
163
|
+
| { type: 'sliceRate'; value: number }
|
|
118
164
|
| { type: 'viewType'; value: 'feature' | 'speed' | 'height' | 'width' | 'fan' | 'temp' | 'filament' }
|
|
119
165
|
| { type: 'paintMode'; value: 'off' | 'enforcer' | 'blocker' | 'material' }
|
|
120
166
|
| { type: 'layerCount'; value: number }
|
|
121
167
|
| { type: 'layerRange'; value: { lo: number; hi: number } }
|
|
168
|
+
/** The move scrub's position within the top shown layer; `max` is that layer's move count, extrusions and
|
|
169
|
+
* travels together. Not fired while the whole layer is shown. */
|
|
170
|
+
| { type: 'moveScrub'; value: { at: number; max: number } }
|
|
122
171
|
| { type: 'error'; value: string }
|
|
123
172
|
| { type: 'notice'; value: string }
|
|
124
173
|
|
package/types/worker.d.ts
CHANGED
|
@@ -13,6 +13,14 @@ export interface BrushArgs {
|
|
|
13
13
|
hx: number; hy: number; hz: number
|
|
14
14
|
cx: number; cy: number; cz: number
|
|
15
15
|
radius: number
|
|
16
|
+
/**
|
|
17
|
+
* The PREVIOUS sample of this stroke. Present, the kernel paints the capsule swept from there to `(hx,hy,hz)`
|
|
18
|
+
* instead of a ball at the hit — which is what upstream does between two consecutive mouse positions
|
|
19
|
+
* (`DoublePointCursor`, GLGizmoPainterBase.cpp:878). A pointer stream is sampled, not continuous, so without it
|
|
20
|
+
* a fast drag lands as a row of separate blobs. Omit on the first sample of a stroke; a kernel built before the
|
|
21
|
+
* capsule existed ignores it and paints the ball, so it is safe to always send.
|
|
22
|
+
*/
|
|
23
|
+
px?: number; py?: number; pz?: number
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
/**
|
|
@@ -56,6 +64,23 @@ export type SlicerRequest =
|
|
|
56
64
|
| { cmd: 'clear'; states?: PaintState[] }
|
|
57
65
|
/** The painted overlay triangles. `enf`/`blk` always; `states` adds those states. Reply: `overlay`. */
|
|
58
66
|
| { cmd: 'overlay'; states?: PaintState[] }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* What a fill tool WOULD select under the cursor, without marking anything — upstream draws this on every mouse
|
|
70
|
+
* move while a fill is active (GLGizmoPainterBase.cpp:929) so a click is aimed rather than tried. No state
|
|
71
|
+
* argument at all: a preview marks nothing, so there is no NONE for a stray boolean to become.
|
|
72
|
+
* `{clear:true}` drops the standing selection. Reply: `fillPreview`.
|
|
73
|
+
*/
|
|
74
|
+
| { cmd: 'fillPreview'; facet?: number; hx?: number; hy?: number; hz?: number
|
|
75
|
+
tool?: PaintTool; angle?: number; clear?: boolean }
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Brush-wide options, set once and read by every stroke and fill — upstream keeps both on the gizmo.
|
|
79
|
+
* `overhangDeg` restricts painting to facets overhanging by more than that angle (`0` = off, the default);
|
|
80
|
+
* `clipPlane` is the section plane `[nx, ny, nz, offset]` in KERNEL coords that every cursor clips against
|
|
81
|
+
* (`null` turns it off), which is the only way a brush reaches an interior surface. Reply: `paintMode`.
|
|
82
|
+
*/
|
|
83
|
+
| { cmd: 'paintMode'; overhangDeg?: number; clipPlane?: [number, number, number, number] | null }
|
|
59
84
|
/**
|
|
60
85
|
* Legacy single-object SLA (resin) slice. `params` accepts a JSON string or an object (this path parses either). Same reply stream as
|
|
61
86
|
* a slice: `progress` and `layer` while it runs, then `done` (stats only; layers arrived as `layer`) — or `error`.
|
|
@@ -65,11 +90,12 @@ export type SlicerRequest =
|
|
|
65
90
|
| { cmd: 'slaJob'; job: SlaJob }
|
|
66
91
|
/**
|
|
67
92
|
* Slice — and note it carries **no `cmd`**: slicing is the worker's default action, and leaving the field off
|
|
68
|
-
* is what selects it. `params`
|
|
69
|
-
*
|
|
93
|
+
* is what selects it. `params` accepts a JSON string or an object; the kernel parses JSON text, so an object is
|
|
94
|
+
* stringified for you (it was string-only through 0.2.2, which made this the one call path with a different
|
|
95
|
+
* contract from `createSlicer().slice()` and `createSlicerClient().slice()`).
|
|
70
96
|
* Replies: `progress` and `layer` while it runs, then `done` — or `error`.
|
|
71
97
|
*/
|
|
72
|
-
| { stl: ArrayBuffer; params: string }
|
|
98
|
+
| { stl: ArrayBuffer; params: string | object }
|
|
73
99
|
|
|
74
100
|
export type SlicerResponse =
|
|
75
101
|
| { type: 'warm' }
|
|
@@ -82,6 +108,9 @@ export type SlicerResponse =
|
|
|
82
108
|
/** `supported: false` from a kernel built before the export binding existed — it does not throw. */
|
|
83
109
|
| { type: 'paintExport'; supported: boolean; facets: number[]; hex: string }
|
|
84
110
|
| { type: 'overlay'; enf: Float32Array; blk: Float32Array; overlays?: Record<number, Float32Array> }
|
|
111
|
+
/** `supported` false on a kernel built before the preview existed; `tris` is flat x,y,z, 3 vertices per triangle. */
|
|
112
|
+
| { type: 'fillPreview'; supported: boolean; tris: Float32Array }
|
|
113
|
+
| { type: 'paintMode'; ok: boolean }
|
|
85
114
|
/**
|
|
86
115
|
* Sent once, unprompted, by the multithreaded kernel only: the address of the support-progress counter and of
|
|
87
116
|
* the cancel flag, inside a SharedArrayBuffer. Writing 1 to the cancel flag stops a slice the worker is
|
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
|
|
42
|
-
`
|
|
43
|
-
|
|
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`, `
|
|
67
|
-
`sliceBar
|
|
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`,
|
|
82
|
-
`error`, `notice`. Initial values are not announced — the host passed them.
|
|
83
|
-
second while slicing.
|
|
84
|
-
- **`onSliced`** — `{plate, stats, gcode}` when a slice is cached. Switching plate tabs does not
|
|
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
|
|
|
@@ -116,6 +180,26 @@ arrows and Delete working.
|
|
|
116
180
|
| `Z` · `B` | Zoom to all · zoom to bed |
|
|
117
181
|
| `Esc` | Back to Prepare |
|
|
118
182
|
|
|
183
|
+
While a brush is open these take over the same letters — the object shortcuts come back the moment it closes.
|
|
184
|
+
|
|
185
|
+
| Painting | |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| `C` · `S` | Brush shape: circle (the visible surface) · sphere (reaches through a thin wall) |
|
|
188
|
+
| `F` · `B` · `T` | Smart fill · bucket fill · single triangle |
|
|
189
|
+
| `V` · `H` | Lock the stroke to a vertical · horizontal line — press again to release |
|
|
190
|
+
| `1`…`9` | Paint with that filament (material brush only) |
|
|
191
|
+
| `Shift + drag` | Erase instead of paint |
|
|
192
|
+
| Drag off the model | Rotate the camera — a press that misses the model is not the brush's, so orbiting never means closing it |
|
|
193
|
+
| `Ctrl/⌘ + wheel` | Brush radius, or the fill angle for a fill tool — the bare wheel stays the camera zoom |
|
|
194
|
+
| `Alt + wheel` | Scrub the section plane, which is what lets a brush reach a surface inside the model |
|
|
195
|
+
| `Esc` | Close the brush, and put back whatever the section plane cut away |
|
|
196
|
+
|
|
197
|
+
The brush draws itself while it is open — a ring for the circle cursor, a translucent ball for the sphere — and a
|
|
198
|
+
fill tool shades what a click would flood before you click it. A drag paints the **capsule** swept between samples
|
|
199
|
+
rather than one ball per sample, so a fast stroke is continuous instead of a row of blobs. A press that misses the
|
|
200
|
+
model falls through to the camera, so the view can be turned mid-paint without closing the brush. The support brush also
|
|
201
|
+
offers *on overhangs only*, which restricts every stroke to facets steeper than the support threshold.
|
|
202
|
+
|
|
119
203
|
Mouse selection follows upstream's rules: a plain click **replaces** the selection, `Ctrl/⌘ + click` adds or
|
|
120
204
|
removes, a plain click on something already selected **keeps** the set (which is what makes dragging several
|
|
121
205
|
objects work), `Shift + drag` is a box select, and clicking empty space clears. Alt is not a de-select, in
|
|
@@ -209,8 +293,8 @@ download.
|
|
|
209
293
|
### Taking the saves
|
|
210
294
|
|
|
211
295
|
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
|
|
213
|
-
handled it:
|
|
296
|
+
browser's download folder. It sees every save: the 3mf project, the STL, each plate's G-code, an SLA plate's
|
|
297
|
+
`.sl1` archive, and preset files. Return truthy to say you handled it:
|
|
214
298
|
|
|
215
299
|
```jsx
|
|
216
300
|
<Viewport onExport={(file, filename) => { uploadToServer(file, filename); return true }} />
|
|
@@ -223,11 +307,23 @@ Return nothing and the download happens as well, which is useful for logging wha
|
|
|
223
307
|
The component owns its scene, and the props are the whole interface — there is no ref, no imperative handle, and no
|
|
224
308
|
`models` prop. Worth knowing before you design around it:
|
|
225
309
|
|
|
226
|
-
- **Models enter through the UI
|
|
227
|
-
|
|
228
|
-
for every object as the set
|
|
229
|
-
|
|
230
|
-
|
|
310
|
+
- **Models enter through the UI, or once at mount through `files`** — after mount, runtime loading stays with the
|
|
311
|
+
file dialog and drag and drop. A host cannot remove or transform an object, or hand a mesh in mid-session. What
|
|
312
|
+
it *can* do is watch: the `objects` event reports `{id, name, extruder, visible}` for every object as the set
|
|
313
|
+
changes.
|
|
314
|
+
- **Slicing is triggered from the UI, by `defaultAutoSlice`, or by bumping `sliceRequest`** — a token prop whose
|
|
315
|
+
identity change requests one slice of the current plate (the mount value is inert), which is what a host with
|
|
316
|
+
the built-in chrome hidden wires its own Slice button to. The result arrives on `onSliced`.
|
|
317
|
+
- **The prime tower stand-in appears only when the plate actually changes tools** — objects assigned to more
|
|
318
|
+
than one extruder, or material paint reaching extruder 2 or higher. Loading a second filament is not enough:
|
|
319
|
+
with everything on T1 the slice emits no tool change and builds no tower, so drawing the box would promise one.
|
|
320
|
+
- **A settings change discards every slice result.** Everything a slice puts on screen — the toolpaths, the stats
|
|
321
|
+
card, the layer slider, the G-code export — describes the settings it ran with, and none of it says so. So when
|
|
322
|
+
a value in `settings` changes, the results are dropped and the viewer returns to Prepare; with `autoSlice` on
|
|
323
|
+
the re-slice then fills the empty preview. Two exceptions, neither of which claims to be a slice of these
|
|
324
|
+
settings: a plate injected through `gcode` / `sl1`, and an opened `.sl1` archive (that import applies the
|
|
325
|
+
archive's own settings, so invalidating on it would erase what the file was opened to show). Comparison is by
|
|
326
|
+
value, so a host that rebuilds its settings object every render does not lose its preview to it.
|
|
231
327
|
- **`gcode` is one-way**: pass G-code text and it is drawn on the selected plate instead of a slice result, and
|
|
232
328
|
auto re-slice leaves that plate alone while it is set.
|
|
233
329
|
- Everything else the component owns — camera, selection, plate count, paint state — is reported through `onEvent`
|