three-slicer 0.2.4 → 0.3.0

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +9 -4
  3. package/THIRD-PARTY-NOTICES.md +7 -2
  4. package/components/dist/SettingsPanel.js +12 -176
  5. package/data/config-schema-lean.json +1 -0
  6. package/data/ui-tree-keys.json +1 -0
  7. package/engine/src/client.js +12 -2
  8. package/engine/src/data.js +6 -0
  9. package/engine/src/settings.js +12 -627
  10. package/engine/src/slicer.worker.js +6 -1
  11. package/engine/src/slicer_core.js +0 -0
  12. package/engine/src/slicer_core.mt.js +0 -0
  13. package/engine/src/toggle_eval.js +9 -77
  14. package/package.json +20 -10
  15. package/types/client.d.ts +5 -2
  16. package/types/components.d.ts +3 -29
  17. package/types/settings.d.ts +3 -0
  18. package/types/toggle.d.ts +2 -22
  19. package/types/viewer-gcode.d.ts +2 -29
  20. package/types/viewer-loaders.d.ts +1 -33
  21. package/types/viewer-toolpath.d.ts +3 -113
  22. package/types/viewer.d.ts +6 -174
  23. package/types/worker.d.ts +2 -1
  24. package/viewer/dist/Viewport.js +9 -5489
  25. package/viewer/dist/gcode_reexport.js +1 -0
  26. package/viewer/dist/loaders_reexport.js +1 -0
  27. package/viewer/dist/toolpath_reexport.js +1 -0
  28. package/components/README.md +0 -44
  29. package/components/styles.css +0 -84
  30. package/engine/src/kernel_setting_keys.js +0 -132
  31. package/engine/src/preset_file.js +0 -99
  32. package/viewer/README.md +0 -375
  33. package/viewer/dist/gcode_parse.js +0 -190
  34. package/viewer/dist/make_worker.js +0 -31
  35. package/viewer/dist/model_loaders.js +0 -184
  36. package/viewer/dist/parse_3mf-C2X_W442.js +0 -300
  37. package/viewer/dist/parse_3mf.worker.js +0 -10
  38. package/viewer/dist/sl1_encode.worker.js +0 -10
  39. package/viewer/dist/sl1_write-UHmQ_itC.js +0 -170
  40. package/viewer/dist/sla_reconstruct-CXkYhux2.js +0 -176
  41. package/viewer/dist/sla_reconstruct.worker.js +0 -24
  42. package/viewer/dist/sla_slice.worker.js +0 -74
  43. package/viewer/dist/toolpath_gpu-4foc4FaU.js +0 -482
  44. package/viewer/dist/toolpath_gpu.js +0 -13
  45. package/viewer/styles.css +0 -353
@@ -28,17 +28,22 @@ const paramsText = (params) => (typeof params === 'string' ? params : JSON.strin
28
28
  let quiet = false
29
29
  const say = (level, ...args) => { if (!quiet) console[level](...args) }
30
30
 
31
+ // Which variant loaded is also REPORTED (the 'warm' reply carries `kernel`), not just logged: mt vs st is a
32
+ // measured 9.8x on a 3M-facet model, and the viewer sizes its plate-parallel worker pool by it.
33
+ let kernelKind = null
31
34
  const loadCore = async () => {
32
35
  const isolated = typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated
33
36
  if (isolated) {
34
37
  try {
35
38
  const M = await (await import('./slicer_core.mt.js')).default()
36
39
  say('info', '[slicer.worker] core: mt (threads)')
40
+ kernelKind = 'mt'
37
41
  return M
38
42
  } catch (e) { say('warn', '[slicer.worker] mt load failed — falling back to st:', e) }
39
43
  }
40
44
  const M = await (await import('./slicer_core.js')).default()
41
45
  say('info', '[slicer.worker] core: st')
46
+ kernelKind = 'st'
42
47
  return M
43
48
  }
44
49
 
@@ -213,7 +218,7 @@ self.onmessage = async (e) => {
213
218
  if (!modPromise) modPromise = loadCore()
214
219
  const Module = await modPromise
215
220
  // Warmup: only load the kernel (+ spawn the mt pthread pool) ahead of time — removes the perceived load on the first slice
216
- if (d.cmd === 'warmup') { self.postMessage({ type: 'warm' }); return }
221
+ if (d.cmd === 'warmup') { self.postMessage({ type: 'warm', kernel: kernelKind }); return }
217
222
  // Stage 20: manual support painting — selector state persists in this worker Module (slicing uses the same Module).
218
223
  // `keepPaint` says the mesh is the same model in a new place, so the marks carry over — the reply reports whether
219
224
  // they actually did, since a face-count change makes the kernel fall back to a clean registration.
Binary file
Binary file
@@ -1,77 +1,9 @@
1
- // Stage 25 S5.1: partial translation of toggle-rules — upstream C++ toggle_options conditions (enable_if) -> JS.
2
- // Scope: among the rules touching keys the viewer handles, only those "fully" translatable with the hardcoded locals below.
3
- // Untranslatable ones (unknown locals, enum comparisons, …) fail open (stay enabled) — avoids wrongly disabling (honest partial implementation).
4
- // Translating all 907 keys / 231 rules is out of scope (noted in the README). Reference: toggle_print_fff_options.locals in toggle-rules.json.
5
- import { toggleRules as toggles, schema } from './data.js'
6
-
7
- // Settings value accessor (settings map first, schema default otherwise). Percentages ("15%") become numbers.
8
- export function makeCfg(settings) {
9
- const val = k => {
10
- let v = settings ? settings[k] : undefined
11
- if (v === undefined || v === '') { const d = schema[k]?.default; v = Array.isArray(d) ? d[0] : d }
12
- return v
13
- }
14
- const num = k => { const v = val(k); const n = parseFloat(v); return Number.isFinite(n) ? n : 0 }
15
- return {
16
- bool: k => { const v = val(k); return v === true || v === 1 || v === '1' || v === 'true' },
17
- int: k => Math.trunc(num(k)),
18
- float: k => num(k),
19
- has: k => k in schema,
20
- }
21
- }
22
-
23
- // Upstream locals translated inline (viewer-related conditions only). Each value is a boolean.
24
- function makeLocals(cfg) {
25
- const have_raft = cfg.int('raft_layers') > 0
26
- const has_spiral_vase = cfg.bool('spiral_mode')
27
- const has_top_shell = cfg.int('top_shell_layers') > 0 || (has_spiral_vase && cfg.int('bottom_shell_layers') > 1)
28
- const has_bottom_shell = cfg.int('bottom_shell_layers') > 0
29
- return {
30
- have_raft,
31
- have_perimeters: cfg.int('wall_loops') > 0,
32
- have_infill: cfg.float('sparse_infill_density') > 0,
33
- has_spiral_vase,
34
- has_top_shell,
35
- has_bottom_shell,
36
- has_solid_infill: has_top_shell || has_bottom_shell,
37
- have_skirt: cfg.int('skirt_loops') > 0,
38
- have_support_material: cfg.bool('enable_support') || have_raft,
39
- have_support_interface: cfg.int('support_interface_top_layers') > 0 || cfg.int('support_interface_bottom_layers') > 0,
40
- have_prime_tower: cfg.bool('enable_prime_tower'),
41
- }
42
- }
43
-
44
- // Evaluates enable_if (up to the first comma) to a boolean. Returns null (= stays enabled) when not fully translatable.
45
- export function evalEnableIf(expr, locals, cfg) {
46
- let s = String(expr).split(',')[0].trim()
47
- if (!s) return null
48
- // Direct config access pattern -> value substitution
49
- s = s.replace(/config->opt_bool\(\s*"([^"]+)"\s*\)/g, (_, k) => cfg.bool(k) ? 'true' : 'false')
50
- s = s.replace(/config->opt_int\(\s*"([^"]+)"\s*\)/g, (_, k) => String(cfg.int(k)))
51
- s = s.replace(/config->opt_float\(\s*"([^"]+)"\s*\)/g, (_, k) => String(cfg.float(k)))
52
- // Substitute known locals (longest name first — prevents partial matches)
53
- for (const name of Object.keys(locals).sort((a, b) => b.length - a.length))
54
- s = s.replace(new RegExp('\\b' + name + '\\b', 'g'), locals[name] ? 'true' : 'false')
55
- // Check the remaining string is a safe boolean expression (only true/false/numbers/operators/parens) — otherwise unknown -> null
56
- const stripped = s.replace(/\btrue\b|\bfalse\b/g, '1')
57
- if (!/^[\s()!<>=&|0-9.]*$/.test(stripped)) return null
58
- try { return !!Function('"use strict";return (' + s + ')')() } catch { return null }
59
- }
60
-
61
- // Map of keys disabled under the current settings { key: condition }. Only rules that are unambiguously false.
62
- export function disabledKeys(cfg) {
63
- const locals = makeLocals(cfg)
64
- const groups = ['toggle_print_fff_options', 'toggle_filament_options', 'toggle_printer_options',
65
- 'TabFilament::toggle_options', 'TabPrinter::toggle_options']
66
- const out = {}
67
- for (const gname of groups) {
68
- const g = toggles[gname]
69
- if (!g || !Array.isArray(g.rules)) continue
70
- for (const rule of g.rules) {
71
- if (!rule.enable_if || !Array.isArray(rule.fields)) continue
72
- const en = evalEnableIf(rule.enable_if, locals, cfg)
73
- if (en === false) for (const k of rule.fields) if (!(k in out)) out[k] = rule.enable_if
74
- }
75
- }
76
- return out
77
- }
1
+ // three-slicer/toggle — the evaluator from the permissive package, bound to upstream's toggle rules.
2
+ // The rules (toggle-rules.json, extracted from OrcaSlicer's toggle_options) and the full schema are this
3
+ // package's data; the evaluator itself is three-slicer-viewer's. Same surface as before the split.
4
+ import { makeToggle, evalEnableIf } from 'three-slicer-viewer/toggle'
5
+ import { toggleRules, schema } from './data.js'
6
+ const bound = makeToggle(toggleRules, schema)
7
+ export const makeCfg = bound.makeCfg
8
+ export const disabledKeys = bound.disabledKeys
9
+ export { evalEnableIf }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-slicer",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Browser/WASM 3D-printing slicer reverse-engineered from OrcaSlicer: slicing engine (Arachne walls, tree/grid supports, multi-material), SLA resin slicing with PrusaSlicer's ported support/pad chain and .sl1 export, React viewer with GPU toolpath preview, and schema-driven settings UI.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -39,15 +39,15 @@
39
39
  },
40
40
  "./viewer/toolpath": {
41
41
  "types": "./types/viewer-toolpath.d.ts",
42
- "default": "./viewer/dist/toolpath_gpu.js"
42
+ "default": "./viewer/dist/toolpath_reexport.js"
43
43
  },
44
44
  "./viewer/loaders": {
45
45
  "types": "./types/viewer-loaders.d.ts",
46
- "default": "./viewer/dist/model_loaders.js"
46
+ "default": "./viewer/dist/loaders_reexport.js"
47
47
  },
48
48
  "./viewer/gcode": {
49
49
  "types": "./types/viewer-gcode.d.ts",
50
- "default": "./viewer/dist/gcode_parse.js"
50
+ "default": "./viewer/dist/gcode_reexport.js"
51
51
  },
52
52
  "./components": {
53
53
  "types": "./types/components.d.ts",
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "sideEffects": false,
67
67
  "scripts": {
68
- "build": "node types/gen_settings_types.mjs && node types/gen_kernel_params.mjs && vite build --config components/vite.config.js && vite build --config viewer/vite.config.js && cp viewer/src/make_worker.js viewer/dist/make_worker.js",
68
+ "build": "node types/gen_settings_types.mjs && node types/gen_kernel_params.mjs && vite build --config components/vite.config.js && vite build --config viewer/vite.config.js",
69
69
  "check": "bash pack_check.sh"
70
70
  },
71
71
  "peerDependencies": {
@@ -74,9 +74,15 @@
74
74
  "three": "^0.160.0"
75
75
  },
76
76
  "peerDependenciesMeta": {
77
- "react": { "optional": true },
78
- "react-dom": { "optional": true },
79
- "three": { "optional": true }
77
+ "react": {
78
+ "optional": true
79
+ },
80
+ "react-dom": {
81
+ "optional": true
82
+ },
83
+ "three": {
84
+ "optional": true
85
+ }
80
86
  },
81
87
  "devDependencies": {
82
88
  "typescript": "^5.6.0",
@@ -93,6 +99,8 @@
93
99
  "viewer/styles.css",
94
100
  "components/styles.css",
95
101
  "data",
102
+ "data/ui-tree-keys.json",
103
+ "data/config-schema-lean.json",
96
104
  "!data/config-schema-builddump.json",
97
105
  "!data/**/.omc",
98
106
  "README.md",
@@ -127,6 +135,8 @@
127
135
  "3d",
128
136
  "mesh",
129
137
  "clipping"
130
- ]
138
+ ],
139
+ "dependencies": {
140
+ "three-slicer-viewer": "0.3.0"
141
+ }
131
142
  }
132
-
package/types/client.d.ts CHANGED
@@ -35,8 +35,8 @@ export interface SlicerClient {
35
35
  /** The worker being driven — for a host that also wants to listen in. */
36
36
  worker: Worker
37
37
 
38
- /** Load the kernel ahead of the first slice. */
39
- warmup(): Promise<void>
38
+ /** Load the kernel ahead of the first slice. Resolves with which variant loaded ('mt' threads / 'st'). */
39
+ warmup(): Promise<{ kernel: 'mt' | 'st' | null }>
40
40
 
41
41
  /** `params` may be an object; it is stringified for you, which the raw protocol does not do. */
42
42
  slice(stl: ArrayBuffer | Uint8Array, params: object | string, cb?: ClientSliceCallbacks): Promise<SliceResult>
@@ -86,3 +86,6 @@ export interface SlicerClient {
86
86
  */
87
87
  export function createSlicerClient(worker?: Worker): SlicerClient
88
88
  export default createSlicerClient
89
+
90
+ /** The WASM kernel's worker, as a factory — what `three-slicer-viewer`'s `useSlicer` takes as `deps.makeWorker`. */
91
+ export function makeSlicerWorker(): Worker
@@ -1,29 +1,3 @@
1
- // three-slicer/components
2
- import type * as React from 'react'
3
- import type { SlicerSettings } from './settings-keys.d.ts'
4
-
5
- export interface SettingsPanelProps {
6
- /** Sparse map — only edited keys. Missing keys fall back to the config-schema default. */
7
- settings: SlicerSettings
8
- /** React setState shape. The only channel through which state leaves the component. */
9
- setSettings: React.Dispatch<React.SetStateAction<SlicerSettings>>
10
- /** Label click — for deep links / detail views. Omit for a plain label (zero router coupling). */
11
- onOptionOpen?: (optKey: string) => void
12
- /** When embedding inside another panel */
13
- embedded?: boolean
14
- /** Replace schema-driven widgets per key (e.g. `{ printable_area: MyBedEditor }`) */
15
- customWidgets?: Record<string, React.ComponentType<any>>
16
- /**
17
- * Pins the panel to one builder (optionally one page) and drops the search/group/page/mode chrome, so the form
18
- * can be folded into another card. This is how `<Viewport/>`'s `motionPanel` slot is meant to be filled:
19
- *
20
- * ```jsx
21
- * <SettingsPanel settings={settings} setSettings={setSettings} embedded
22
- * only={{ builder: 'TabPrinter::build_kinematics_page' }} />
23
- * ```
24
- */
25
- only?: { builder: string; page?: string }
26
- }
27
-
28
- declare const SettingsPanel: React.FC<SettingsPanelProps>
29
- export default SettingsPanel
1
+ // three-slicer/components — the permissive settings panel with upstream's data plugged in. Same surface.
2
+ export * from 'three-slicer-viewer/components'
3
+ export { default } from 'three-slicer-viewer/components'
@@ -201,3 +201,6 @@ export interface FilamentPresetsApi {
201
201
  settingsFor(presetName: string): SlicerSettings | null
202
202
  }
203
203
  export function filamentPresets(): Promise<FilamentPresetsApi>
204
+
205
+ /** The nine catalog lookups above as one object — what `three-slicer-viewer`'s `<Viewport catalog>` takes. */
206
+ export const bundledCatalog: import('three-slicer-viewer').Catalog
package/types/toggle.d.ts CHANGED
@@ -1,22 +1,2 @@
1
- // three-slicer/toggle — partial evaluation of the enable_if expressions in toggle-rules.json.
2
- import type { SlicerSettings } from './settings-keys.d.ts'
3
-
4
- /** Settings accessor — schema default when a value is missing, [0] for vector types. */
5
- export interface ToggleCfg {
6
- bool(key: string): boolean
7
- int(key: string): number
8
- float(key: string): number
9
- /** Whether the key exists in config-schema */
10
- has(key: string): boolean
11
- }
12
-
13
- export function makeCfg(settings: SlicerSettings | null | undefined): ToggleCfg
14
-
15
- /**
16
- * Evaluates a single enable_if expression. Untranslatable expressions return `null` (= fail-open, stays enabled) —
17
- * make sure to treat that as distinct from `false`.
18
- */
19
- export function evalEnableIf(expr: string, locals: Record<string, unknown>, cfg: ToggleCfg): boolean | null
20
-
21
- /** Keys that must be disabled under the current settings -> the enable_if expression that disabled them. Only rules that are unambiguously false. */
22
- export function disabledKeys(cfg: ToggleCfg): Record<string, string>
1
+ // three-slicer/toggle — the permissive evaluator bound to upstream's toggle rules. Same surface.
2
+ export * from 'three-slicer-viewer/toggle'
@@ -1,29 +1,2 @@
1
- // three-slicer/viewer/gcode — G-code text -> kernel-shaped layer stream (renderable without slicing).
2
- // Feed the returned layers straight into buildSegmentData (three-slicer/viewer/toolpath).
3
-
4
- export interface GcodeLayer {
5
- z: number
6
- /** Stride 8: [x0,y0,z0,enc, x1,y1,z1,enc] per segment, enc = role + tool*16 (role 0 = travel). */
7
- paths: Float32Array
8
- /** One entry per segment; 0 means "use the default line width". */
9
- widths: Float32Array
10
- }
11
-
12
- export interface ParseGcodeResult {
13
- layers: GcodeLayer[]
14
- stats: { layers: number; path_segments: number; travel_segments: number; tools: number[] }
15
- }
16
-
17
- export interface ParseGcodeOptions {
18
- /** Used to derive bead width from E when the file has no ;WIDTH: comments. Default 1.75. */
19
- filamentDiameter?: number
20
- /** Bead-height fallback when a layer's height cannot be inferred from z steps. Default 0.2. */
21
- defaultLayerHeight?: number
22
- }
23
-
24
- /**
25
- * Parses G0/G1/G2/G3 (I/J arcs), G90/G91, M82/M83, G92, T<n>, and the layer/role comment conventions of
26
- * OrcaSlicer/PrusaSlicer (;TYPE: ;WIDTH: ;LAYER_CHANGE ;Z:), Cura (;TYPE: ;LAYER:) and this kernel's own output.
27
- * Recovery is lossy where G-code carries no data: unknown roles become wall(1), widths are derived from E.
28
- */
29
- export function parseGcode(text: string, opts?: ParseGcodeOptions): ParseGcodeResult
1
+ // Re-exported from the permissive package (MIT), where the implementation now lives.
2
+ export * from 'three-slicer-viewer/gcode'
@@ -1,33 +1 @@
1
- // three-slicer/viewer/loaders — pure loaders, independent of the kernel.
2
-
3
- /** The 5 built-ins plus any extension registered via `registerLoader()`, which pushes onto this array. */
4
- export const SUPPORTED_EXT: string[]
5
-
6
- /** Lowercased extension. Empty string when there is no dot. */
7
- export function fileExt(name: string): string
8
-
9
- export interface LoadedObject {
10
- name: string
11
- /** Triangle vertex coordinates (N*9, model z-up) */
12
- modelPos: Float32Array
13
- }
14
-
15
- /** A single file may contain several objects (3mf/amf/step) -> always an array. */
16
- export function loadModel(name: string, buffer: ArrayBuffer): Promise<LoadedObject[]>
17
-
18
- /**
19
- * Registers an extra format. Formats needing heavy dependencies (STEP=OCCT WASM, etc.) are not bundled here,
20
- * so the app wires them up itself. The `<Viewport/>` file dialog and drag-and-drop filters pick them up automatically.
21
- *
22
- * ```js
23
- * import { registerLoader } from 'three-slicer/viewer/loaders'
24
- * registerLoader('step,stp', async (buffer, name) => [{ name, modelPos }])
25
- * ```
26
- */
27
- export function registerLoader(
28
- exts: string | string[],
29
- fn: (buffer: ArrayBuffer, name: string) => LoadedObject[] | Promise<LoadedObject[]>,
30
- ): void
31
-
32
- /** Connected-component split. Returns `null` when there is only one component (cannot split) — not an empty array. */
33
- export function splitConnectedComponents(localPos: Float32Array): Float32Array[] | null
1
+ export * from 'three-slicer-viewer/loaders'
@@ -1,113 +1,3 @@
1
- // three-slicer/viewer/toolpath — kernel layers -> GPU-instanced toolpath.
2
- // Does not import three: makeToolpath takes the THREE namespace as an argument (guaranteeing a single instance).
3
-
4
- /** The 24 triangle indices of the 8-vertex diamond cross-section (from SegmentTemplate.cpp:18) */
5
- export const VERTEX_DATA: number[]
6
-
7
- /** Toolpath type -> color. 0=travel, 1=wall … 11=prime */
8
- export const TYPE_COLOR: Record<number, number[]>
9
- export const TYPE_LABEL: Record<number, string>
10
-
11
- /**
12
- * Per-extruder colors for the Filament view. Categorical, not semantic — the toolpath stream carries a tool index and
13
- * nothing about the material, and a machine may have more extruders than entries, so index it modulo its length.
14
- */
15
- export const TOOL_COLOR: number[][]
16
-
17
- /** Blue-to-red 11-color heatmap (libvgcode ColorRange.hpp:14) */
18
- export const DEFAULT_RANGES_COLORS: number[][]
19
-
20
- export interface ViewTypeDef {
21
- key: 'feature' | 'speed' | 'height' | 'width' | 'fan' | 'temp' | 'filament'
22
- label: string
23
- /** Whether values are continuous (heatmap). False means a fixed color. */
24
- cont: boolean
25
- unit: string
26
- }
27
- export const VIEW_TYPES: ViewTypeDef[]
28
-
29
- /** Per-vertex extra data */
30
- export interface SegmentMeta {
31
- vType: Uint8Array
32
- /** Printing extruder, decoded out of the role field's high bits. Untooled output decodes to 0, i.e. single material. */
33
- vTool: Uint8Array
34
- vWidth: Float32Array
35
- vHeight: Float32Array
36
- vLayer: Int32Array
37
- }
38
-
39
- export interface SegmentData {
40
- position: Float32Array
41
- hwa: Float32Array
42
- segIndex: Uint32Array
43
- nV: number
44
- nSeg: number
45
- layerSegPrefix: Uint32Array
46
- travelPos: Float32Array
47
- travelPrefix: Uint32Array
48
- nTrav: number
49
- layerCount: number
50
- maxAbs: number
51
- hasNaN: boolean
52
- meta: SegmentMeta
53
- /** Total extruded length per type (index = toolpath type, length 16) */
54
- typeLengths: Float64Array
55
- /** Null when there is not a single vertex */
56
- bbox: { min: [number, number, number]; max: [number, number, number] } | null
57
- }
58
-
59
- /** Kernel layers[{z, paths(stride8), widths[]}] -> GPU stream. */
60
- export function buildSegmentData(layers: unknown[], defaultLineWidth: number): SegmentData
61
-
62
- /** Length share per type (%), descending by pct. The kernel does not expose time per role, so length is used as an approximation. */
63
- export function roleRatios(typeLengths: Float64Array | number[]): Array<{
64
- type: number
65
- label: string
66
- pct: number
67
- color: number[]
68
- }>
69
-
70
- /**
71
- * speed/fan/temp are absent from the kernel toolpath and derived from settings — these are the values used for that.
72
- */
73
- export interface ColorContext {
74
- speedByType?: Record<number, number>
75
- firstLayerSpeed?: number
76
- fanByType?: Record<number, number>
77
- fanFirstLayers?: number
78
- tempNormal?: number
79
- tempFirst?: number
80
- closeFanLayers?: number
81
- }
82
-
83
- export interface ColorResult {
84
- /** Per-vertex RGBA (nV*4). The color is packed into `.r`. */
85
- color: Float32Array
86
- min: number
87
- max: number
88
- viewType: ViewTypeDef['key']
89
- label: string
90
- unit: string
91
- /** False means fixed colors — min/max are 0 and no legend should be drawn. */
92
- cont: boolean
93
- }
94
-
95
- export function computeColors(data: SegmentData, viewType: ViewTypeDef['key'], ctx: ColorContext): ColorResult
96
-
97
- export interface ToolpathHandle {
98
- /** THREE.Mesh — typed by the THREE namespace passed in */
99
- mesh: any
100
- /** THREE.LineSegments (travel) */
101
- travLines: any
102
- setLayerRange(lo: number, hi: number): void
103
- /** Backwards compatible — same as setLayerRange(0, n-1). */
104
- setVisibleLayers(n: number): void
105
- setTravelVisible(visible: boolean): void
106
- setColors(color: Float32Array): void
107
- dispose(): void
108
- nSeg: number
109
- layerCount: number
110
- }
111
-
112
- /** @param THREE - the `import * as THREE from 'three'` namespace. The consumer's instance is used as-is. */
113
- export function makeToolpath(THREE: any, data: SegmentData): ToolpathHandle
1
+ // Re-exported from the permissive package (MIT), where the implementation now lives.
2
+ // three-slicer/viewer/toolpath stays a valid entry point for every existing consumer.
3
+ export * from 'three-slicer-viewer'
package/types/viewer.d.ts CHANGED
@@ -1,175 +1,7 @@
1
- // three-slicer/viewer
2
- import type * as React from 'react'
3
- import type { SlicerSettings } from './settings-keys.d.ts'
4
-
5
- export interface ViewportProps {
6
- /** Sparse settings map. Defaults to `{}`. */
7
- settings?: SlicerSettings
8
- /** React setState shape. Defaults to a no-op. */
9
- setSettings?: React.Dispatch<React.SetStateAction<SlicerSettings>>
10
- /** Left process-panel slot — usually `<SettingsPanel embedded/>`. Defaults to null. */
11
- processPanel?: React.ReactNode
12
- /** Motion-limits editor, folded into the printer card — usually `<SettingsPanel only={{builder:'TabPrinter::build_kinematics_page'}}/>`. */
13
- motionPanel?: React.ReactNode
14
- /**
15
- * Filament editor, folded into the filament card. Pass a function rather than a node: with more than one
16
- * extruder loaded the card projects that extruder's slice of the per-extruder columns and writes the form's
17
- * edits back at its index, so the panel must bind to the pair it is given.
18
- */
19
- filamentPanel?: React.ReactNode
20
- | ((settings: SlicerSettings, setSettings: React.Dispatch<React.SetStateAction<SlicerSettings>>) => React.ReactNode)
21
- /**
22
- * Per-panel visibility. Every panel is visible unless its key is explicitly `false`, so a host only opts out —
23
- * and a panel added in a later version does not disappear for hosts that listed the ones they wanted.
24
- *
25
- * A {@link LockablePanel} also accepts `'readonly'`: the panel is drawn but nothing inside it can be clicked,
26
- * typed into or tabbed to — including any node the host passed into it. That is the shape a host wants when it
27
- * presets the printer, process and filament itself and does not want the user changing them.
28
- *
29
- * Note what it locks: the UI path, not the value. `settings` is still the host's own prop, and other paths
30
- * inside the viewer still write it — importing a 3mf project applies that project's settings either way.
31
- */
32
- panels?: Partial<Record<ViewportPanel, boolean>> & Partial<Record<LockablePanel, boolean | 'readonly'>>
33
- /**
34
- * Behaviour switches, following the same opt-out rule as {@link ViewportProps.panels}: everything is on unless
35
- * its key is explicitly `false`. These are the things the component does *outside* its own box — take the page's
36
- * keyboard, load the WASM kernel, write to the console — which a host embedding it in a larger app may already
37
- * be doing itself. See {@link ViewportFeature}.
38
- */
39
- features?: Partial<Record<ViewportFeature, boolean>>
40
- /**
41
- * Intercepts every file the viewer would otherwise hand to the browser as a download — the 3mf project, the STL,
42
- * and each plate's G-code. Return anything truthy to say it is handled and suppress the download; return nothing
43
- * to let it proceed as well. This is the only way in: the save buttons live inside the component.
44
- *
45
- * ```jsx
46
- * <Viewport onExport={(blob, filename) => { upload(blob, filename); return true }} />
47
- * ```
48
- */
49
- onExport?: (file: Blob, filename: string) => boolean | void
50
- /**
51
- * G-code text to render instead of a slice result. Parsed into the same layer stream the kernel produces and
52
- * shown on the selected plate; the kernel is never started. While it is set, auto re-slice leaves that plate alone.
53
- */
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
-
86
- /** Initial filament colours (hex), one per extruder. Defaults to the built-in T1/T2 pair. */
87
- defaultExtruderColors?: string[] | null
88
- /**
89
- * Start with auto re-slice on. Unlike the in-app toggle's original behaviour this also performs the FIRST slice,
90
- * which is what makes a panel-less embed able to slice at all.
91
- */
92
- defaultAutoSlice?: boolean
93
- /** Every value change in one channel. See {@link ViewportEvent}. */
94
- onEvent?: (event: ViewportEvent) => void
95
- /** A finished slice, fired where the result is cached — switching plate tabs does not re-fire it. */
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
102
- }
103
-
104
- /**
105
- * Behaviour keys accepted by {@link ViewportProps.features}. All default to enabled.
106
- *
107
- * - `shortcuts` — the keyboard bindings. They are installed on `window`, so while the viewer is mounted they fire
108
- * wherever focus is (except inside inputs) and `Ctrl+C` in particular preventDefaults. Turn them off when the
109
- * host page has its own.
110
- * - `warmup` — loading the WASM kernel on mount, ahead of the first slice. Off, nothing is downloaded or compiled
111
- * until something actually slices — which for a `gcode`-only viewer is never.
112
- * - `drop` — drag and drop onto the canvas.
113
- * - `filePicker` — the file dialog, from every button that opens it.
114
- * - `contextMenu` — the right-click menu. Note that the browser's own menu stays suppressed over the canvas either
115
- * way: OrbitControls preventDefaults `contextmenu` itself.
116
- * - `logs` — console diagnostics, from the component and from the slice worker.
117
- */
118
- export type ViewportFeature = 'shortcuts' | 'warmup' | 'drop' | 'filePicker' | 'contextMenu' | 'logs'
119
-
120
- /**
121
- * The panels that accept `'readonly'` as well as `true`/`false` — the right column and the cards in it. Everything
122
- * else takes a boolean only: locking the gizmo rail or the plate tabs is what `features` and `panels: false` are
123
- * for, and pretending otherwise would mean a `'readonly'` that silently does nothing on half the keys.
124
- *
125
- * `sidebar: 'readonly'` locks the whole column in one go, so the per-card keys are for mixing — a locked printer
126
- * card above a live object list, say.
127
- */
128
- export type LockablePanel =
129
- | 'sidebar' | 'printerCard' | 'filamentCard' | 'objectList' | 'towerCard'
130
- | 'previewControls' | 'processCard' | 'sliceBar'
131
-
132
- /** Panel keys accepted by {@link ViewportProps.panels}. `sidebar: false` hides the whole right column at once. */
133
- export type ViewportPanel =
134
- | 'topBar' | 'gizmoRail' | 'objectToolbar' | 'paintPanel' | 'statsCard' | 'plateBar' | 'emptyHint' | 'status'
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'
139
- /** The over-the-bed warning, shown in Prepare when something leaves the printable area. */
140
- | 'bedWarn'
141
- /** The prime tower card. Only rendered anyway once a second filament exists. */
142
- | 'towerCard'
143
- /** The resin (SLA) card. Takes the filament card's place when the printer profile declares SLA. */
144
- | 'resinCard'
145
-
146
- /** Value changes reported through {@link ViewportProps.onEvent}. Not fired for the initial values. */
147
- export type ViewportEvent =
148
- | { type: 'canvasMode'; value: 'prepare' | 'preview' }
149
- | { type: 'objects'; value: Array<{ id: number; name: string; extruder: number; visible: boolean }> }
150
- | { type: 'selectedPlate'; value: number }
151
- | { type: 'plateCount'; value: number }
152
- | { type: 'extruderColors'; value: string[] }
153
- | { type: 'autoSlice'; value: boolean }
154
- | { type: 'slicing'; value: boolean }
155
- /** 0..1. Fires several times a second while slicing — throttle on the host side if that matters. */
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 }
164
- | { type: 'viewType'; value: 'feature' | 'speed' | 'height' | 'width' | 'fan' | 'temp' | 'filament' }
165
- | { type: 'paintMode'; value: 'off' | 'enforcer' | 'blocker' | 'material' }
166
- | { type: 'layerCount'; value: number }
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 } }
171
- | { type: 'error'; value: string }
172
- | { type: 'notice'; value: string }
173
-
174
- declare const Viewport: React.FC<ViewportProps>
1
+ // three-slicer/viewer — the permissive viewer (three-slicer-viewer, MIT) with the kernel plugged in.
2
+ export * from 'three-slicer-viewer'
3
+ import type { ViewportProps, SlicerHandle } from 'three-slicer-viewer'
4
+ /** The permissive `useSlicer` bound to the WASM kernel worker (three-slicer/client's makeSlicerWorker). */
5
+ export function useSlicer(deps: Record<string, unknown>): SlicerHandle
6
+ declare function Viewport(props: ViewportProps): JSX.Element
175
7
  export default Viewport