three-slicer-viewer 0.2.5

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.
@@ -0,0 +1,173 @@
1
+ // three-slicer-viewer/settings — the catalog-free settings surface (three-slicer/settings re-exports it and adds the vendor catalog).
2
+ import type { SlicerSettings, SettingKey, Point } from './settings-keys.d.ts'
3
+ export type { SlicerSettings, SettingKey, Point }
4
+
5
+ /** The config-schema default value (undefined for unknown keys) */
6
+ export function schemaDefault(key: string): unknown
7
+
8
+ /** Settings map first, schema default otherwise. Vector types are returned as-is. */
9
+ export function settingRaw(settings: SlicerSettings | null | undefined, key: string): unknown
10
+
11
+ /** Normalizes settingRaw to a scalar — [0] for vectors. */
12
+ export function settingScalar(settings: SlicerSettings | null | undefined, key: string): unknown
13
+
14
+ /**
15
+ * Settings map -> kernel slice params. Derived from schema keys; missing keys are filled with internal defaults.
16
+ * ponytail: 53 returned keys, so Record instead of listing each — kernel params are passed straight to slice(),
17
+ * not an API consumers read field by field. Expand it when field access is actually needed.
18
+ */
19
+ export function deriveKernelParams(settings: SlicerSettings | null | undefined, opts?: { plate?: number }): Record<string, unknown>
20
+
21
+ /**
22
+ * Clear-then-merge preset application: deletes `keys` (the catalog's own key set) from `settings`, then
23
+ * merges `preset`. A null preset applies nothing and returns the settings unchanged — a failed lookup must
24
+ * not half-clear the previous pick.
25
+ */
26
+ export function applyPreset(settings: SlicerSettings | null | undefined, preset: Partial<SlicerSettings> | null | undefined, keys: readonly string[] | null | undefined): SlicerSettings
27
+
28
+ /**
29
+ * The schema keys deriveKernelParams consumes — the distinct "From setting" column of engine/PARAMS.md,
30
+ * generated by the same probe that generates the table (so it cannot drift from the real mapping).
31
+ */
32
+ export const kernelSettingKeys: readonly string[]
33
+
34
+ /**
35
+ * The keys of `settings` that deriveKernelParams does NOT read — they are accepted and silently have no
36
+ * effect on an FFF slice. Scope is the FFF derivation only: a key listed here can still be meaningful to
37
+ * the SLA derivation, the viewer, or project metadata. Sorted.
38
+ */
39
+ export function ignoredKernelSettings(settings: SlicerSettings | null | undefined): string[]
40
+
41
+ /** Which slicing technology the settings map's printer profile declares. Defaults to FFF. */
42
+ export function printerTechnology(settings: SlicerSettings | null | undefined): 'FFF' | 'SLA'
43
+
44
+ /**
45
+ * Settings map -> SLA (resin) slice params for the pure-JS contour slicer + the display geometry the SL1 raster
46
+ * export needs. Separate from deriveKernelParams — that one feeds the WASM kernel and its key set is pinned.
47
+ */
48
+ export function deriveSlaParams(settings: SlicerSettings | null | undefined): {
49
+ layer_height: number
50
+ initial_layer_height: number
51
+ exposure_time: number
52
+ initial_exposure_time: number
53
+ faded_layers: number
54
+ display_width: number
55
+ display_height: number
56
+ display_pixels_x: number
57
+ display_pixels_y: number
58
+ supports_enable: boolean
59
+ pad_enable: boolean
60
+ }
61
+
62
+ /**
63
+ * A 3mf project's raw `Metadata/project_settings.config` -> a settings map this API accepts.
64
+ * Upstream writes every option as a string ("0"/"1" for a bool, "0.2" for a float, one entry per extruder for a
65
+ * vector); each value is coerced by its config-schema type, and keys the schema does not define are dropped into
66
+ * `skipped` rather than carried into the map. `applied` is how many keys survived.
67
+ */
68
+ export function normalizeProjectSettings(raw: Record<string, unknown> | null | undefined): {
69
+ settings: SlicerSettings
70
+ applied: number
71
+ skipped: string[]
72
+ }
73
+
74
+ /**
75
+ * The inverse, for WRITING a 3mf's `Metadata/project_settings.config`: every value back to the string shape
76
+ * upstream serializes (a bool as "1"/"0", a point as "XxY", a points group as "X1xY1,X2xY2"), so another slicer
77
+ * reads what it expects. Keys the config schema does not define are dropped.
78
+ */
79
+ export function serializeProjectSettings(settings: SlicerSettings | null | undefined): Record<string, string | string[]>
80
+
81
+ // ---- Preset files -----------------------------------------------------------
82
+ // The `.json` OrcaSlicer's "Config files" dialog imports and its "Export preset" writes. One file, one preset,
83
+ // one type. The value encoding is the same all-strings shape `project_settings.config` uses — same writer
84
+ // upstream (ConfigBase::save_to_json) — so these reuse the coercion above rather than repeating it.
85
+
86
+ /** Upstream's own `type` field: machine = printer, process = print settings, filament = material. */
87
+ export type PresetType = 'machine' | 'process' | 'filament'
88
+
89
+ /** The option keys that belong in a preset of this type, from upstream's `Preset::printer_options()` and friends. */
90
+ export function presetOptionKeys(type: PresetType): string[]
91
+
92
+ export interface WritePresetOptions {
93
+ /** Defaults to `'machine'`. */
94
+ type?: PresetType
95
+ /** Goes in the file's `name` field, and is what an importer shows. Defaults to `'Custom'`. */
96
+ name?: string
97
+ /**
98
+ * `true` (the default) writes every option of that type, filling unset ones from the config-schema default, so
99
+ * the file stands on its own. `false` writes only what the settings map holds — a diff-shaped file, readable
100
+ * only next to whatever it was derived from.
101
+ */
102
+ complete?: boolean
103
+ version?: string
104
+ }
105
+
106
+ /**
107
+ * A settings map -> the object a preset `.json` holds. Written **without `inherits`**: upstream saves a derived
108
+ * preset as the diff against its parent, which only means something beside the vendor database it came from.
109
+ */
110
+ export function writePresetFile(settings: SlicerSettings | null | undefined, options?: WritePresetOptions): Record<string, unknown>
111
+
112
+ /** The same, already stringified — what an export actually writes to disk. */
113
+ export function presetFileText(settings: SlicerSettings | null | undefined, options?: WritePresetOptions): string
114
+
115
+ export interface ReadPresetResult {
116
+ /** The parent's values with the file's own on top, since the file is the diff. */
117
+ settings: SlicerSettings
118
+ type: PresetType
119
+ name: string
120
+ /** The parent named by the file, or `null`. */
121
+ inherits: string | null
122
+ /**
123
+ * Set to the parent's name when it was named but could not be resolved. The file's own values are still
124
+ * applied — a vendor preset without its parent still carries most of itself, and the caller decides whether
125
+ * that is good enough. Watch for it: the Bambu X1C machine file never states its own bed, so read alone it
126
+ * yields a printer with no `printable_area` at all.
127
+ */
128
+ missingParent: string | null
129
+ applied: number
130
+ skipped: string[]
131
+ }
132
+
133
+ /**
134
+ * A parsed preset `.json` -> a settings map. Pass `resolveParent` to follow `inherits`; `printerSettings` covers
135
+ * the machine case for the keys this package carries.
136
+ */
137
+ export function readPresetFile(raw: Record<string, unknown> | null | undefined,
138
+ options?: { resolveParent?: (name: string, type: PresetType) => SlicerSettings | null }): ReadPresetResult
139
+
140
+ // ---- The vendor catalog: NOT in this package ---------------------------------
141
+ // printerKeys, printersByVendor, printerTechByVendor, resinCatalog, resinSettingsFor, printerSettings,
142
+ // printerDefaultPreset, processPresets and filamentPresets are OrcaSlicer's profile data (AGPL) and live in
143
+ // `three-slicer/settings`. The viewer takes them through its `catalog` prop (see `Catalog` in viewer.d.ts);
144
+ // the interface shapes below are what such a catalog's lazily loaded halves return, kept here so a host can
145
+ // type its own.
146
+
147
+ /** The schema keys the kernel's machine limits are read from — for UI that shows which ones are in play. */
148
+ export const machineLimitKeys: string[]
149
+
150
+ export interface ProcessPresetsApi {
151
+ /** Every key a process preset can set — clear these before applying a different one */
152
+ keys: string[]
153
+ /** Preset names compatible with a printer profile, in upstream order */
154
+ listFor(printerProfileName: string): string[]
155
+ /** The settings a preset applies, ready to merge. `null` when unknown. */
156
+ settingsFor(presetName: string): SlicerSettings | null
157
+ }
158
+
159
+ /** A material as a picker shows it. `type`/`vendor` are `''` when the profile chain declares neither. */
160
+ export interface FilamentPreset { name: string, type: string, vendor: string }
161
+
162
+ export interface FilamentPresetsApi {
163
+ /** Every key a filament preset can set — clear these before applying a different material */
164
+ keys: string[]
165
+ /** Every material in the catalog, in upstream order — for a picker shown before any printer is chosen */
166
+ all(): FilamentPreset[]
167
+ /** Materials compatible with a printer profile, in upstream order */
168
+ listFor(printerProfileName: string): FilamentPreset[]
169
+ /** The vendor's recommended materials for that printer's model — a subset of listFor */
170
+ recommendedFor(printerProfileName: string): FilamentPreset[]
171
+ /** The settings a material applies, ready to merge. `null` when unknown. */
172
+ settingsFor(presetName: string): SlicerSettings | null
173
+ }
@@ -0,0 +1,30 @@
1
+ // three-slicer-viewer/toggle — partial evaluation of enable_if expressions. Ships unbound (no rules: nothing
2
+ // disables); three-slicer/toggle binds upstream's toggle-rules.json and the full schema through makeToggle().
3
+ import type { SlicerSettings } from './settings-keys.d.ts'
4
+
5
+ /** Settings accessor — schema default when a value is missing, [0] for vector types. */
6
+ export interface ToggleCfg {
7
+ bool(key: string): boolean
8
+ int(key: string): number
9
+ float(key: string): number
10
+ /** Whether the key exists in config-schema */
11
+ has(key: string): boolean
12
+ }
13
+
14
+ export function makeCfg(settings: SlicerSettings | null | undefined): ToggleCfg
15
+
16
+ /**
17
+ * Evaluates a single enable_if expression. Untranslatable expressions return `null` (= fail-open, stays enabled) —
18
+ * make sure to treat that as distinct from `false`.
19
+ */
20
+ export function evalEnableIf(expr: string, locals: Record<string, unknown>, cfg: ToggleCfg): boolean | null
21
+
22
+ /** Keys that must be disabled under the current settings -> the enable_if expression that disabled them. Only rules that are unambiguously false. */
23
+ export function disabledKeys(cfg: ToggleCfg): Record<string, string>
24
+
25
+ /** Bind the evaluator to a rule set and a schema. `three-slicer/toggle` is `makeToggle(toggleRules, schema)`. */
26
+ export function makeToggle(rules?: Record<string, { rules?: Array<{ enable_if?: string; fields?: string[] }> }>, schema?: Record<string, { default?: unknown }>): {
27
+ makeCfg(settings: SlicerSettings | null | undefined): ToggleCfg
28
+ disabledKeys(cfg: ToggleCfg): Record<string, string>
29
+ evalEnableIf(expr: string, locals: Record<string, unknown>, cfg: ToggleCfg): boolean | null
30
+ }
@@ -0,0 +1,138 @@
1
+ // three-slicer-viewer — kernel layers -> GPU-instanced toolpath.
2
+ // Also published as three-slicer/viewer/toolpath, which re-exports this package.
3
+ // Does not import three: makeToolpath takes the THREE namespace as an argument (guaranteeing a single instance).
4
+
5
+ /** The 24 triangle indices of the 8-vertex diamond cross-section (from SegmentTemplate.cpp:18) */
6
+ export const VERTEX_DATA: number[]
7
+
8
+ /** Toolpath type -> color. 0=travel, 1=wall … 11=prime */
9
+ export const TYPE_COLOR: Record<number, number[]>
10
+ export const TYPE_LABEL: Record<number, string>
11
+
12
+ /**
13
+ * Per-extruder colors for the Filament view. Categorical, not semantic — the toolpath stream carries a tool index and
14
+ * nothing about the material, and a machine may have more extruders than entries, so index it modulo its length.
15
+ */
16
+ export const TOOL_COLOR: number[][]
17
+
18
+ /** Blue-to-red 11-color heatmap (libvgcode ColorRange.hpp:14) */
19
+ export const DEFAULT_RANGES_COLORS: number[][]
20
+
21
+ export interface ViewTypeDef {
22
+ key: 'feature' | 'speed' | 'height' | 'width' | 'fan' | 'temp' | 'filament'
23
+ label: string
24
+ /** Whether values are continuous (heatmap). False means a fixed color. */
25
+ cont: boolean
26
+ unit: string
27
+ }
28
+ export const VIEW_TYPES: ViewTypeDef[]
29
+
30
+ /** Per-vertex extra data */
31
+ export interface SegmentMeta {
32
+ vType: Uint8Array
33
+ /** Printing extruder, decoded out of the role field's high bits. Untooled output decodes to 0, i.e. single material. */
34
+ vTool: Uint8Array
35
+ vWidth: Float32Array
36
+ vHeight: Float32Array
37
+ vLayer: Int32Array
38
+ }
39
+
40
+ export interface SegmentData {
41
+ position: Float32Array
42
+ hwa: Float32Array
43
+ segIndex: Uint32Array
44
+ nV: number
45
+ nSeg: number
46
+ layerSegPrefix: Uint32Array
47
+ travelPos: Float32Array
48
+ travelPrefix: Uint32Array
49
+ nTrav: number
50
+ layerCount: number
51
+ maxAbs: number
52
+ hasNaN: boolean
53
+ meta: SegmentMeta
54
+ /** Total extruded length per type (index = toolpath type, length 16) */
55
+ typeLengths: Float64Array
56
+ /** Null when there is not a single vertex */
57
+ bbox: { min: [number, number, number]; max: [number, number, number] } | null
58
+ }
59
+
60
+ /** Kernel layers[{z, paths(stride8), widths[]}] -> GPU stream. */
61
+ export function buildSegmentData(layers: unknown[], defaultLineWidth: number): SegmentData
62
+
63
+ /** Length share per type (%), descending by pct. The kernel does not expose time per role, so length is used as an approximation. */
64
+ export function roleRatios(typeLengths: Float64Array | number[]): Array<{
65
+ type: number
66
+ label: string
67
+ pct: number
68
+ color: number[]
69
+ }>
70
+
71
+ /**
72
+ * speed/fan/temp are absent from the kernel toolpath and derived from settings — these are the values used for that.
73
+ */
74
+ export interface ColorContext {
75
+ /** Print speed per toolpath type, mm/s. Falls back to the wall entry for a type not listed. */
76
+ speedByType?: Record<number, number>
77
+ firstLayerSpeed?: number
78
+ /** Part-cooling fan %, used for every layer at or above `closeFanLayers`. Bridges are always 100. */
79
+ fanNormal?: number
80
+ /** Layers below this index are drawn at 0% fan. */
81
+ closeFanLayers?: number
82
+ tempNormal?: number
83
+ tempFirst?: number
84
+ /** Filament colours (hex, per extruder) for the Filament view, so the preview agrees with the filament
85
+ * card and the stats legend. Falls back to the categorical TOOL_COLOR palette per tool. */
86
+ toolColors?: string[]
87
+ }
88
+
89
+ export interface ColorResult {
90
+ /** Per-vertex RGBA (nV*4). The color is packed into `.r`. */
91
+ color: Float32Array
92
+ min: number
93
+ max: number
94
+ viewType: ViewTypeDef['key']
95
+ label: string
96
+ unit: string
97
+ /** False means fixed colors — min/max are 0 and no legend should be drawn. */
98
+ cont: boolean
99
+ }
100
+
101
+ export function computeColors(data: SegmentData, viewType: ViewTypeDef['key'], ctx: ColorContext): ColorResult
102
+
103
+ export interface ToolpathHandle {
104
+ /** THREE.Mesh — typed by the THREE namespace passed in */
105
+ mesh: any
106
+ /** THREE.LineSegments (travel) */
107
+ travLines: any
108
+ setLayerRange(lo: number, hi: number): void
109
+ /** Backwards compatible — same as setLayerRange(0, n-1). */
110
+ setVisibleLayers(n: number): void
111
+ setTravelVisible(visible: boolean): void
112
+ setColors(color: Float32Array): void
113
+ dispose(): void
114
+ nSeg: number
115
+ layerCount: number
116
+ }
117
+
118
+ /** @param THREE - the `import * as THREE from 'three'` namespace. The consumer's instance is used as-is. */
119
+ export function makeToolpath(THREE: any, data: SegmentData): ToolpathHandle
120
+
121
+ /** [r,g,b] in 0..1 -> r<<16 | g<<8 | b, the form the shader unpacks. */
122
+ export function packColor(rgb: number[]): number
123
+ /** '#rrggbb' -> [r,g,b] in 0..1. Unparseable input returns mid-grey rather than NaN. */
124
+ export function hexToRgb(hex: string): number[]
125
+ /** Value -> colour interpolated between adjacent stops. Clamps BOTH ends; non-finite input returns stop 0. */
126
+ export function rangeColorAt(v: number, lo: number, hi: number, palette?: number[][]): number[]
127
+
128
+ /** Moves in one layer — extrusions and travels together, which is what a scrub bar walks. */
129
+ export function layerMoveCount(data: SegmentData, layer: number): number
130
+ /** The topmost layer in [lo, hi] that has moves, or `lo` when none does. */
131
+ export function topMoveLayer(data: SegmentData, lo: number, hi: number): number
132
+ /** Where the nozzle is after `at` moves into `layer`. `segCount + travCount === at`, clamped to the layer. */
133
+ export function moveCursor(data: SegmentData, layer: number, at: number): {
134
+ point: [number, number, number] | null
135
+ segCount: number
136
+ travCount: number
137
+ onTravel: boolean
138
+ }
@@ -0,0 +1,256 @@
1
+ // three-slicer/viewer
2
+ import type * as React from 'react'
3
+ import type { SlicerSettings } from './settings-keys.d.ts'
4
+ import type { ProcessPresetsApi, FilamentPresetsApi } from './settings.d.ts'
5
+
6
+ export interface ViewportProps {
7
+ /**
8
+ * Sparse settings map. Defaults to `{}`. Two viewer knobs ride in it beside the schema keys: `sla_antialias`
9
+ * (SL1 mask MSAA, 1|2|4) and `slice_workers` — how many plates an all-plates run slices at once (0/absent =
10
+ * Auto: half the cores on the threaded kernel, one per core on the single-threaded one; the in-app select
11
+ * writes it). Neither reaches a 3mf.
12
+ */
13
+ settings?: SlicerSettings
14
+ /** React setState shape. Defaults to a no-op. */
15
+ setSettings?: React.Dispatch<React.SetStateAction<SlicerSettings>>
16
+ /**
17
+ * Per-plate overrides: `{[plateIndex]: sparse map}`, merged over `settings` for that plate's slice only —
18
+ * a key's absence means "follow the global value" (delete the key to clear an override). Plate identity is
19
+ * the plate index; deleting the last plate truncates its entry. Editing one plate's override invalidates only
20
+ * that plate's cached result. Any key can be overridden: `printer_technology` routes that plate to the
21
+ * FFF or SLA slicer (an SLA plate's grid cell is sized to its resin display), and a bed override
22
+ * (`printable_area`/`printable_height`) resizes that plate's own grid cell. What a 3mf cannot represent —
23
+ * a mixed-technology or mixed-bed project — is refused at project export with a typed error
24
+ * (`error.code` `'UNSUPPORTED_MIXED_TECH_3MF'` / `'UNSUPPORTED_MIXED_BED_3MF'`); per-plate G-code/.sl1
25
+ * exports always work. Defaults to `{}`.
26
+ */
27
+ plateSettings?: Record<number, SlicerSettings>
28
+ /** React setState shape for {@link ViewportProps.plateSettings}. Defaults to a no-op. */
29
+ setPlateSettings?: React.Dispatch<React.SetStateAction<Record<number, SlicerSettings>>>
30
+ /**
31
+ * Left process-panel slot — usually `<SettingsPanel embedded/>`. Defaults to null.
32
+ *
33
+ * Pass a FUNCTION to opt into the per-plate scope toggle (shown with more than one plate): in plate scope
34
+ * the panel receives the selected plate's EFFECTIVE map and a setter that writes only the changed keys into
35
+ * {@link ViewportProps.plateSettings}; `meta.overriddenKeys`/`meta.onRevertKey` drive the override badges
36
+ * (`<SettingsPanel overriddenKeys={...} onRevertKey={...}/>`). In global scope the pair is simply
37
+ * `settings`/`setSettings`. A plain node keeps the global-only binding.
38
+ */
39
+ processPanel?: React.ReactNode
40
+ | ((settings: SlicerSettings, setSettings: React.Dispatch<React.SetStateAction<SlicerSettings>>,
41
+ meta: { scope: 'global' | 'plate', selectedPlate: number, overriddenKeys: string[],
42
+ onRevertKey: ((key: string) => void) | null }) => React.ReactNode)
43
+ /** Motion-limits editor, folded into the printer card — usually `<SettingsPanel only={{builder:'TabPrinter::build_kinematics_page'}}/>`. */
44
+ motionPanel?: React.ReactNode
45
+ /**
46
+ * Filament editor, folded into the filament card. Pass a function rather than a node: with more than one
47
+ * extruder loaded the card projects that extruder's slice of the per-extruder columns and writes the form's
48
+ * edits back at its index, so the panel must bind to the pair it is given.
49
+ */
50
+ filamentPanel?: React.ReactNode
51
+ | ((settings: SlicerSettings, setSettings: React.Dispatch<React.SetStateAction<SlicerSettings>>) => React.ReactNode)
52
+ /**
53
+ * Per-panel visibility. Every panel is visible unless its key is explicitly `false`, so a host only opts out —
54
+ * and a panel added in a later version does not disappear for hosts that listed the ones they wanted.
55
+ *
56
+ * A {@link LockablePanel} also accepts `'readonly'`: the panel is drawn but nothing inside it can be clicked,
57
+ * typed into or tabbed to — including any node the host passed into it. That is the shape a host wants when it
58
+ * presets the printer, process and filament itself and does not want the user changing them.
59
+ *
60
+ * Note what it locks: the UI path, not the value. `settings` is still the host's own prop, and other paths
61
+ * inside the viewer still write it — importing a 3mf project applies that project's settings either way.
62
+ */
63
+ panels?: Partial<Record<ViewportPanel, boolean>> & Partial<Record<LockablePanel, boolean | 'readonly'>>
64
+ /**
65
+ * Behaviour switches, following the same opt-out rule as {@link ViewportProps.panels}: everything is on unless
66
+ * its key is explicitly `false`. These are the things the component does *outside* its own box — take the page's
67
+ * keyboard, load the WASM kernel, write to the console — which a host embedding it in a larger app may already
68
+ * be doing itself. See {@link ViewportFeature}.
69
+ */
70
+ features?: Partial<Record<ViewportFeature, boolean>>
71
+ /**
72
+ * Intercepts every file the viewer would otherwise hand to the browser as a download — the 3mf project, the STL,
73
+ * and each plate's G-code. Return anything truthy to say it is handled and suppress the download; return nothing
74
+ * to let it proceed as well. This is the only way in: the save buttons live inside the component.
75
+ *
76
+ * ```jsx
77
+ * <Viewport onExport={(blob, filename) => { upload(blob, filename); return true }} />
78
+ * ```
79
+ */
80
+ onExport?: (file: Blob, filename: string) => boolean | void
81
+ /**
82
+ * G-code text to render instead of a slice result. Parsed into the same layer stream the kernel produces and
83
+ * shown on the selected plate; the kernel is never started. While it is set, auto re-slice leaves that plate alone.
84
+ */
85
+ gcode?: string | null
86
+ /**
87
+ * An `.sl1` archive rendered as a raster preview — the SLA half of {@link ViewportProps.gcode}'s contract: it
88
+ * lands on the selected plate and the kernel is never started. Set both and the G-code wins; one plate holds
89
+ * one artifact.
90
+ *
91
+ * Two things it does that `gcode` does not. It writes to your `settings` — the archive's own job description,
92
+ * `printer_technology` included, because an .sl1 opened in an FFF session has to switch it. And it reconstructs
93
+ * a surface from the masks in the background, so the preview fills in a moment after the layers appear.
94
+ *
95
+ * Re-injects whenever the value's IDENTITY changes, so hold the File/buffer in state rather than building it
96
+ * during render — a fresh `new Uint8Array(...)` each render re-imports each render. `{name, data}` exists
97
+ * because the name is the import notice's subject and the filename a re-export hands back.
98
+ */
99
+ sl1?: File | Blob | ArrayBuffer | Uint8Array | { name?: string, data: ArrayBuffer | Uint8Array } | null
100
+ /**
101
+ * Content imported once on mount, through the same extension dispatch as a drop: models (STL/OBJ/3MF/AMF/PLY —
102
+ * a 3mf project applies its settings, plates and painting), `.sl1` raster archives, and preset files
103
+ * (`.json` / `.orca_printer` / `.orca_bundle` / `.orca_filament` / `.zip`). The `{name, data}` form exists
104
+ * because the dispatch runs on the file NAME's extension. Deliberately mount-only — recreating the array on a
105
+ * later render does not re-import; runtime loading is the picker, drop, or a remount.
106
+ */
107
+ files?: Array<File | { name: string, data: ArrayBuffer | Uint8Array }>
108
+ /**
109
+ * The slicing implementation — a React hook with the shape of three-slicer's `useSlicer`. This package ships
110
+ * none: without it the viewer runs on a no-op slicer that loads, displays and exports but never slices.
111
+ * Must be stable for the life of the mount (it is called as a hook).
112
+ */
113
+ slicer?: (deps: Record<string, unknown>) => SlicerHandle
114
+ /**
115
+ * The vendor preset catalog. This package ships none — nothing passed means empty pickers. `three-slicer/viewer`
116
+ * passes its bundled OrcaSlicer extraction; a host with its own fleet passes its own (a partial object is
117
+ * merged over the empty catalog).
118
+ */
119
+ catalog?: Catalog | null
120
+ /**
121
+ * A token whose identity CHANGE requests one slice of the current plate — the host's Slice button:
122
+ * keep a counter in state and bump it. The mount value is inert (0 and null both slice nothing), so is
123
+ * setting it back to `null`. Ignored while a `gcode`/`sl1` injection holds the plate or the scene is
124
+ * empty — the same conditions under which the built-in slice bar is not pressable. A change while a
125
+ * slice is running cancels it and re-slices: the last request wins.
126
+ */
127
+ sliceRequest?: number | string | object | null
128
+
129
+ /** Initial filament colours (hex), one per extruder. Defaults to the built-in T1/T2 pair. */
130
+ defaultExtruderColors?: string[] | null
131
+ /**
132
+ * Start with auto re-slice on. Unlike the in-app toggle's original behaviour this also performs the FIRST slice,
133
+ * which is what makes a panel-less embed able to slice at all.
134
+ */
135
+ defaultAutoSlice?: boolean
136
+ /** Every value change in one channel. See {@link ViewportEvent}. */
137
+ onEvent?: (event: ViewportEvent) => void
138
+ /** A finished slice, fired where the result is cached — switching plate tabs does not re-fire it. */
139
+ /** `throughput` is how fast the slice ran — see `SliceThroughput` in the engine entry. Absent on a kernel
140
+ * result that carried none (a cached plate re-announced from before the field existed). */
141
+ onSliced?: (result: {
142
+ plate: number; stats: Record<string, unknown>; gcode: string
143
+ throughput?: { ms: number; kernelMs: number | null; layersPerSecond: number; msPerMsegment: number | null }
144
+ }) => void
145
+ /**
146
+ * An all-plates run as it moves: the run map on every change, `null` when none is on. `workers.pool` is how
147
+ * many slice at once, `workers.kernel` which kernel loaded ('mt' threads / 'st'); each plate carries its state
148
+ * and its own progress (0..1) and layers/second. Fired several times a second while a run is on.
149
+ */
150
+ onSliceRun?: (run: {
151
+ workers: { pool: number; active: number; kernel: 'mt' | 'st' | null }
152
+ plates: Record<number, { state: 'queued' | 'busy' | 'done' | 'failed'; progress: number; rate: number }>
153
+ } | null) => void
154
+ }
155
+
156
+ /**
157
+ * Behaviour keys accepted by {@link ViewportProps.features}. All default to enabled.
158
+ *
159
+ * - `shortcuts` — the keyboard bindings. They are installed on `window`, so while the viewer is mounted they fire
160
+ * wherever focus is (except inside inputs) and `Ctrl+C` in particular preventDefaults. Turn them off when the
161
+ * host page has its own.
162
+ * - `warmup` — loading the WASM kernel on mount, ahead of the first slice. Off, nothing is downloaded or compiled
163
+ * until something actually slices — which for a `gcode`-only viewer is never.
164
+ * - `drop` — drag and drop onto the canvas.
165
+ * - `filePicker` — the file dialog, from every button that opens it.
166
+ * - `contextMenu` — the right-click menu. Note that the browser's own menu stays suppressed over the canvas either
167
+ * way: OrbitControls preventDefaults `contextmenu` itself.
168
+ * - `logs` — console diagnostics, from the component and from the slice worker.
169
+ */
170
+ export type ViewportFeature = 'shortcuts' | 'warmup' | 'drop' | 'filePicker' | 'contextMenu' | 'logs'
171
+
172
+ /**
173
+ * The panels that accept `'readonly'` as well as `true`/`false` — the right column and the cards in it. Everything
174
+ * else takes a boolean only: locking the gizmo rail or the plate tabs is what `features` and `panels: false` are
175
+ * for, and pretending otherwise would mean a `'readonly'` that silently does nothing on half the keys.
176
+ *
177
+ * `sidebar: 'readonly'` locks the whole column in one go, so the per-card keys are for mixing — a locked printer
178
+ * card above a live object list, say.
179
+ */
180
+ export type LockablePanel =
181
+ | 'sidebar' | 'printerCard' | 'filamentCard' | 'objectList' | 'towerCard'
182
+ | 'previewControls' | 'processCard' | 'sliceBar'
183
+
184
+ /** Panel keys accepted by {@link ViewportProps.panels}. `sidebar: false` hides the whole right column at once. */
185
+ export type ViewportPanel =
186
+ | 'topBar' | 'gizmoRail' | 'objectToolbar' | 'paintPanel' | 'statsCard' | 'plateBar' | 'emptyHint' | 'status'
187
+ | 'sidebar' | 'printerCard' | 'filamentCard' | 'objectList' | 'previewControls' | 'processCard' | 'sliceBar'
188
+ /** The horizontal move scrub under the canvas — how far into the top shown layer the print has got. Preview
189
+ * and FFF only: a resin layer is cured in one exposure and has no intra-layer order to walk. */
190
+ | 'moveBar'
191
+ /** The over-the-bed warning, shown in Prepare when something leaves the printable area. */
192
+ | 'bedWarn'
193
+ /** The prime tower card. Only rendered anyway once a second filament exists. */
194
+ | 'towerCard'
195
+ /** The resin (SLA) card. Takes the filament card's place when the printer profile declares SLA. */
196
+ | 'resinCard'
197
+
198
+ /** Value changes reported through {@link ViewportProps.onEvent}. Not fired for the initial values. */
199
+ export type ViewportEvent =
200
+ | { type: 'canvasMode'; value: 'prepare' | 'preview' }
201
+ | { type: 'objects'; value: Array<{ id: number; name: string; extruder: number; visible: boolean }> }
202
+ | { type: 'selectedPlate'; value: number }
203
+ | { type: 'plateCount'; value: number }
204
+ | { type: 'extruderColors'; value: string[] }
205
+ | { type: 'autoSlice'; value: boolean }
206
+ | { type: 'slicing'; value: boolean }
207
+ /** 0..1. Fires several times a second while slicing — throttle on the host side if that matters. */
208
+ | { type: 'progress'; value: number }
209
+ /**
210
+ * Live throughput: layers finished per second over a 250ms window, `0` between slices. An SLA slice reports it
211
+ * throughout. An FFF slice reports it only once the emission pass begins streaming layers — the earlier passes
212
+ * publish no per-layer progress, so nothing is measured there and none is invented. For the whole-slice figure
213
+ * use `throughput` from `onSliced`.
214
+ */
215
+ | { type: 'sliceRate'; value: number }
216
+ | { type: 'viewType'; value: 'feature' | 'speed' | 'height' | 'width' | 'fan' | 'temp' | 'filament' }
217
+ | { type: 'paintMode'; value: 'off' | 'enforcer' | 'blocker' | 'material' }
218
+ | { type: 'layerCount'; value: number }
219
+ | { type: 'layerRange'; value: { lo: number; hi: number } }
220
+ /** The move scrub's position within the top shown layer; `max` is that layer's move count, extrusions and
221
+ * travels together. Not fired while the whole layer is shown. */
222
+ | { type: 'moveScrub'; value: { at: number; max: number } }
223
+ | { type: 'error'; value: string }
224
+ | { type: 'notice'; value: string }
225
+
226
+ declare const Viewport: React.FC<ViewportProps>
227
+ export default Viewport
228
+
229
+ export interface SlicerHandle {
230
+ getWorker(): Worker | { postMessage(msg: unknown): void }
231
+ cancelSlice(): void
232
+ runSlice(merged: unknown, ctx?: unknown): Promise<unknown>
233
+ pendingSliceRef: { current: unknown }
234
+ downgradeRef: { current: boolean }
235
+ createPoolContext(opts: Record<string, unknown>): { cancel(): void; terminate(): void }
236
+ kernelKindRef: { current: 'mt' | 'st' | null }
237
+ progressSinkRef: { current: unknown }
238
+ }
239
+ export interface Catalog {
240
+ printerKeys: string[]
241
+ printerSettings(profileName: string): Record<string, unknown> | null
242
+ printersByVendor: Record<string, unknown>
243
+ printerTechByVendor: Record<string, unknown>
244
+ printerDefaultPreset(profileName: string): string | null
245
+ processPresets(): Promise<ProcessPresetsApi>
246
+ filamentPresets(): Promise<FilamentPresetsApi>
247
+ resinCatalog: unknown[]
248
+ resinSettingsFor(name: string): Record<string, unknown> | null
249
+ }
250
+ export const emptyCatalog: Catalog
251
+ export function resolveCatalog(catalog?: Partial<Catalog> | null): Catalog
252
+ export function useNoopSlicer(): SlicerHandle
253
+
254
+ /** The slicing hook, unbound. `deps.makeWorker` must return a Worker speaking this package's slice protocol —
255
+ * three-slicer/viewer supplies its WASM kernel worker and re-exports the bound hook. */
256
+ export function useSlicer(deps: Record<string, unknown> & { makeWorker: () => Worker }): SlicerHandle