three-slicer 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +342 -24
- package/components/README.md +1 -1
- package/components/dist/SettingsPanel.js +111 -112
- package/components/styles.css +9 -9
- package/data/invalidation-map.json +1 -1
- package/engine/examples/headless.mjs +2 -2
- package/engine/index.d.ts +3 -1
- package/engine/index.js +2 -2
- package/engine/src/data.js +12 -0
- package/engine/src/settings.js +35 -35
- package/engine/src/slicer.worker.js +25 -22
- package/engine/src/slicer_core.js +0 -0
- package/engine/src/slicer_core.mt.js +0 -0
- package/engine/src/toggle_eval.js +12 -13
- package/package.json +54 -26
- package/types/components.d.ts +19 -0
- package/types/data/config-schema.json.d.ts +55 -0
- package/types/data/invalidation-map.json.d.ts +15 -0
- package/types/data/toggle-rules.json.d.ts +20 -0
- package/types/data/ui-tree.json.d.ts +19 -0
- package/types/data.d.ts +17 -0
- package/types/gen_settings_types.mjs +70 -0
- package/types/settings-keys.d.ts +1704 -0
- package/types/settings.d.ts +19 -0
- package/types/toggle.d.ts +22 -0
- package/types/viewer-loaders.d.ts +33 -0
- package/types/viewer-toolpath.d.ts +105 -0
- package/types/viewer.d.ts +15 -0
- package/types/wasm.d.ts +5 -0
- package/types/worker.d.ts +3 -0
- package/viewer/README.md +1 -1
- package/viewer/dist/Viewport.js +1774 -923
- package/viewer/dist/make_worker.js +6 -6
- package/viewer/dist/model_loaders.js +221 -109
- package/viewer/dist/toolpath_gpu.js +146 -147
- package/viewer/styles.css +27 -25
- package/data/config-schema-builddump.json +0 -12921
- package/viewer/src/Viewport.jsx +0 -1353
- package/viewer/src/assets/add.svg +0 -1
- package/viewer/src/assets/arrange.svg +0 -1
- package/viewer/src/assets/boolean.svg +0 -1
- package/viewer/src/assets/cut.svg +0 -1
- package/viewer/src/assets/delete.svg +0 -1
- package/viewer/src/assets/deleteall.svg +0 -1
- package/viewer/src/assets/duplicate.svg +0 -1
- package/viewer/src/assets/measure.svg +0 -1
- package/viewer/src/assets/mmu.svg +0 -1
- package/viewer/src/assets/move.svg +0 -1
- package/viewer/src/assets/negative.svg +0 -1
- package/viewer/src/assets/onbed.svg +0 -1
- package/viewer/src/assets/open.svg +0 -1
- package/viewer/src/assets/orient.svg +0 -1
- package/viewer/src/assets/paint.svg +0 -1
- package/viewer/src/assets/rotate.svg +0 -1
- package/viewer/src/assets/scale.svg +0 -1
- package/viewer/src/assets/seam.svg +0 -1
- package/viewer/src/assets/split.svg +0 -1
- package/viewer/src/assets/textmark.svg +0 -1
- package/viewer/src/assets/varlayer.svg +0 -1
- package/viewer/src/icons.js +0 -23
- package/viewer/src/make_worker.js +0 -9
- package/viewer/src/model_loaders.js +0 -178
- package/viewer/src/shadow_host.jsx +0 -30
- package/viewer/src/toolpath_gpu.js +0 -430
package/engine/src/settings.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
//
|
|
2
|
-
// -
|
|
3
|
-
// -
|
|
4
|
-
import schema from '
|
|
1
|
+
// Maps the actual values of the (editable) right-hand settings panel -> kernel slice parameters.
|
|
2
|
+
// - Settings state is a sparse map (key->value): only edited keys are stored, otherwise the config-schema default.
|
|
3
|
+
// - For vector types (coFloats/coInts, …) only the first element is used/edited (simplification).
|
|
4
|
+
import { schema } from './data.js'
|
|
5
5
|
|
|
6
6
|
export function schemaDefault(key) { return schema[key]?.default }
|
|
7
7
|
export function settingRaw(settings, key) { return (settings && key in settings) ? settings[key] : schemaDefault(key) }
|
|
8
|
-
//
|
|
8
|
+
// Normalize to a scalar ([0] for vectors)
|
|
9
9
|
export function settingScalar(settings, key) { const v = settingRaw(settings, key); return Array.isArray(v) ? v[0] : v }
|
|
10
10
|
|
|
11
|
-
// 8
|
|
11
|
+
// Stage 8: real ported Fill patterns (gyroid TPMS/honeycomb/3dhoneycomb/crosshatch/concentric) + the older approximations
|
|
12
12
|
const KERNEL_PATTERNS = ['rectilinear', 'grid', 'triangles', 'zigzag', 'gyroid', 'gyroid_approx',
|
|
13
13
|
'honeycomb', '3dhoneycomb', 'crosshatch', 'concentric']
|
|
14
14
|
const KERNEL_SEAMS = ['nearest', 'aligned', 'back', 'random']
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// Right-panel settings values -> kernel parameters (derived from schema keys)
|
|
17
17
|
export function deriveKernelParams(settings) {
|
|
18
18
|
const S = k => settingScalar(settings, k)
|
|
19
19
|
const num = (k, d) => { const v = Number(S(k)); return Number.isFinite(v) ? v : d }
|
|
20
20
|
const bool = (k, d) => { const v = settingRaw(settings, k); return typeof v === 'boolean' ? v : (Array.isArray(v) ? !!v[0] : (v == null ? d : !!v)) }
|
|
21
21
|
|
|
22
|
-
let line_width = num('line_width', 0); if (!line_width) line_width = 0.42 // 0=auto
|
|
22
|
+
let line_width = num('line_width', 0); if (!line_width) line_width = 0.42 // 0=auto -> default 0.42
|
|
23
23
|
|
|
24
24
|
let pat = String(S('sparse_infill_pattern') ?? 'rectilinear')
|
|
25
|
-
if (!KERNEL_PATTERNS.includes(pat)) pat = 'rectilinear' //
|
|
25
|
+
if (!KERNEL_PATTERNS.includes(pat)) pat = 'rectilinear' // pattern unsupported by the kernel -> rectilinear
|
|
26
26
|
|
|
27
27
|
let seam = String(S('seam_position') ?? 'back')
|
|
28
28
|
if (seam === 'aligned_back') seam = 'back'
|
|
29
29
|
if (!KERNEL_SEAMS.includes(seam)) seam = 'back'
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// generate_tree_support_3D
|
|
31
|
+
// Support style: schema enum (default/grid/snug/organic/tree_slim/tree_strong/tree_hybrid)
|
|
32
|
+
// -> kernel grid|tree|tree_lite. Since stage 18 the tree/organic family maps to the real organic TreeSupport ('tree',
|
|
33
|
+
// generate_tree_support_3D -> real type5 branch toolpaths); everything else maps to grid.
|
|
34
34
|
const styleRaw = String(S('support_style') ?? 'default')
|
|
35
35
|
const support_style = /tree|organic/i.test(styleRaw) ? 'tree' : 'grid'
|
|
36
36
|
|
|
37
|
-
//
|
|
37
|
+
// Bed: bounding box of the first rectangle in printable_area
|
|
38
38
|
const pa = settingRaw(settings, 'printable_area')
|
|
39
39
|
let bed_width = 256, bed_depth = 256
|
|
40
40
|
if (Array.isArray(pa) && pa.length) {
|
|
@@ -42,8 +42,8 @@ export function deriveKernelParams(settings) {
|
|
|
42
42
|
bed_width = Math.max(...xs) - Math.min(...xs); bed_depth = Math.max(...ys) - Math.min(...ys)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
// 21
|
|
46
|
-
// 0
|
|
45
|
+
// Stage 21: per-feature widths — passed to the kernel verbatim (including strings like "120%"). Unedited keys are omitted -> the kernel derives auto(=line_width).
|
|
46
|
+
// The 0=auto semantics are handled by the kernel's resolve_lw (upstream Flow formula). Defaults unchanged (no feature set -> line_width).
|
|
47
47
|
const widths = {}
|
|
48
48
|
for (const k of ['outer_wall_line_width','inner_wall_line_width','top_surface_line_width',
|
|
49
49
|
'sparse_infill_line_width','internal_solid_infill_line_width','initial_layer_line_width']) {
|
|
@@ -63,12 +63,12 @@ export function deriveKernelParams(settings) {
|
|
|
63
63
|
seam_position: seam,
|
|
64
64
|
skirt_loops: num('skirt_loops', 1),
|
|
65
65
|
skirt_distance: num('skirt_distance', 2),
|
|
66
|
-
skirt_height: num('skirt_height', 1), // 33
|
|
66
|
+
skirt_height: num('skirt_height', 1), // stage 33: the kernel used to hardcode the first layer
|
|
67
67
|
brim_width: num('brim_width', 0),
|
|
68
|
-
brim_object_gap: num('brim_object_gap', 0), // 33
|
|
69
|
-
retract_length: num('retraction_length', 0.8), //
|
|
70
|
-
retraction_minimum_travel: num('retraction_minimum_travel', 2), // 33
|
|
71
|
-
gcode_resolution: num('resolution', 0.01), // 33
|
|
68
|
+
brim_object_gap: num('brim_object_gap', 0), // stage 33: the kernel used to hardcode w*0.5
|
|
69
|
+
retract_length: num('retraction_length', 0.8), // vector[0]
|
|
70
|
+
retraction_minimum_travel: num('retraction_minimum_travel', 2), // stage 33: used to be the kernel constant 2.0
|
|
71
|
+
gcode_resolution: num('resolution', 0.01), // stage 33: tree-support path simplification tolerance
|
|
72
72
|
retract_speed: num('retraction_speed', 30),
|
|
73
73
|
z_hop: num('z_hop', 0.4),
|
|
74
74
|
travel_speed: num('travel_speed', 120),
|
|
@@ -78,15 +78,15 @@ export function deriveKernelParams(settings) {
|
|
|
78
78
|
filament_diameter: num('filament_diameter', 1.75),
|
|
79
79
|
flow_ratio: num('filament_flow_ratio', 1.0),
|
|
80
80
|
nozzle_temp: num('nozzle_temperature', 200),
|
|
81
|
-
bed_temp: num('hot_plate_temp', 45), // bed_temperature
|
|
81
|
+
bed_temp: num('hot_plate_temp', 45), // no bed_temperature key -> hot_plate_temp
|
|
82
82
|
bed_width, bed_depth,
|
|
83
83
|
enable_support: bool('enable_support', false),
|
|
84
84
|
support_threshold_angle: num('support_threshold_angle', 30),
|
|
85
85
|
support_top_z_distance: num('support_top_z_distance', 0.2),
|
|
86
|
-
support_bottom_z_distance: num('support_bottom_z_distance', 0.2), // 32
|
|
86
|
+
support_bottom_z_distance: num('support_bottom_z_distance', 0.2), // stage 32: support bottom z-gap (default 0.2 = equivalent to current behavior)
|
|
87
87
|
support_xy_distance: num('support_object_xy_distance', 0.35),
|
|
88
88
|
support_interface_top_layers: num('support_interface_top_layers', 2),
|
|
89
|
-
// 33
|
|
89
|
+
// Stage 33: support keys added when the kernel hardcoding was removed (upstream schema key names kept)
|
|
90
90
|
support_angle: num('support_angle', 0),
|
|
91
91
|
support_base_pattern: String(S('support_base_pattern') ?? 'default'),
|
|
92
92
|
support_interface_pattern: String(S('support_interface_pattern') ?? 'auto'),
|
|
@@ -97,34 +97,34 @@ export function deriveKernelParams(settings) {
|
|
|
97
97
|
support_expansion: num('support_expansion', 0),
|
|
98
98
|
support_threshold_overlap: (() => { const v = settingRaw(settings, 'support_threshold_overlap')
|
|
99
99
|
if (typeof v === 'string' && v.trim().endsWith('%')) return parseFloat(v) / 100
|
|
100
|
-
const n = Number(v); return Number.isFinite(n) ? n : 0.5 })(), // "50%"
|
|
100
|
+
const n = Number(v); return Number.isFinite(n) ? n : 0.5 })(), // "50%" -> 0.5 (ratio of extrusion width)
|
|
101
101
|
support_on_build_plate_only: bool('support_on_build_plate_only', false),
|
|
102
102
|
support_interface_bottom_layers: num('support_interface_bottom_layers', 0),
|
|
103
103
|
raft_layers: num('raft_layers', 0),
|
|
104
|
-
raft_expansion: num('raft_expansion', 1.5), // 33
|
|
105
|
-
raft_contact_distance: num('raft_contact_distance', 0.1), // 33
|
|
106
|
-
fan_speed: num('fan_max_speed', 100), // fan_speed
|
|
104
|
+
raft_expansion: num('raft_expansion', 1.5), // stage 33: the kernel used to hardcode +3.0
|
|
105
|
+
raft_contact_distance: num('raft_contact_distance', 0.1), // stage 33: previously ignored by the kernel
|
|
106
|
+
fan_speed: num('fan_max_speed', 100), // no fan_speed key -> fan_max_speed
|
|
107
107
|
close_fan_the_first_x_layers: num('close_fan_the_first_x_layers', 1),
|
|
108
108
|
full_fan_speed_layer: num('full_fan_speed_layer', 0),
|
|
109
109
|
slow_down_layer_time: num('slow_down_layer_time', 5),
|
|
110
110
|
enable_arc_fitting: bool('enable_arc_fitting', false),
|
|
111
111
|
spiral_mode: bool('spiral_mode', false),
|
|
112
|
-
// 5
|
|
113
|
-
seam_slope_type: String(S('seam_slope_type') ?? 'none'), // none|external|all
|
|
112
|
+
// New in stage 5 (derived from the matching schema keys)
|
|
113
|
+
seam_slope_type: String(S('seam_slope_type') ?? 'none'), // none|external|all -> scarf seam
|
|
114
114
|
enable_pressure_advance: bool('enable_pressure_advance', false),
|
|
115
115
|
pressure_advance: num('pressure_advance', 0.02),
|
|
116
|
-
support_style, // grid|tree_lite (
|
|
116
|
+
support_style, // grid|tree_lite (mapped above)
|
|
117
117
|
bridge_speed: num('bridge_speed', 25),
|
|
118
|
-
// 6
|
|
119
|
-
//
|
|
120
|
-
ironing_type: String(S('ironing_type') ?? 'no ironing'), // no ironing|top|topmost|solid (top
|
|
118
|
+
// New in stage 6 (derived from the matching schema keys). The MM parameters (extruder_count/mm_group_split) are
|
|
119
|
+
// injected at onSlice time from the viewer's per-object extruder assignment (not included here).
|
|
120
|
+
ironing_type: String(S('ironing_type') ?? 'no ironing'), // no ironing|top|topmost|solid (the top family = on)
|
|
121
121
|
ironing_spacing: num('ironing_spacing', 0.1),
|
|
122
122
|
ironing_flow: num('ironing_flow', 10),
|
|
123
123
|
ironing_speed: num('ironing_speed', 20),
|
|
124
124
|
reduce_crossing_wall: bool('reduce_crossing_wall', false),
|
|
125
125
|
max_volumetric_extrusion_rate_slope: num('max_volumetric_extrusion_rate_slope', 0),
|
|
126
|
-
// 7
|
|
126
|
+
// Stage 7: wall generator classic|arachne (arachne = the real ported OrcaSlicer WallToolPaths, variable width)
|
|
127
127
|
wall_generator: (String(S('wall_generator') ?? 'classic') === 'arachne') ? 'arachne' : 'classic',
|
|
128
|
-
// support_density
|
|
128
|
+
// support_density has no matching schema key, so the kernel default (0.15) is used; scarf_length likewise uses the kernel default (10mm)
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Runs slicing off the main thread (non-blocking UI) plus stage-30 layer streaming.
|
|
2
2
|
// Vite module worker: new Worker(new URL('./slicer.worker.js', import.meta.url), { type: 'module' }).
|
|
3
|
-
// SINGLE_FILE
|
|
3
|
+
// SINGLE_FILE means the wasm is inlined into slicer_core.js -> no external fetch from the worker either.
|
|
4
4
|
//
|
|
5
|
-
// 30
|
|
6
|
-
// (Float32Array
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
5
|
+
// Stage 30 (OOM tolerance): set_layer_sink lets the kernel emit layers as it produces them; each is transferred to main
|
|
6
|
+
// immediately (Float32Array buffers moved -> the worker copy is released at once) and the kernel frees that layer buffer
|
|
7
|
+
// from the heap. The resident result (the full gw.s + the full layersArr) disappears, so the WASM heap peak drops sharply
|
|
8
|
+
// -> large models avoid OOM. The final 'done' carries stats only.
|
|
9
|
+
// In economy mode (params.economy) only G-code chunks are emitted, without toolpaths (finishes without a preview).
|
|
10
|
+
// The MM / real-PE paths fall back to batch mode.
|
|
11
|
+
// Automatic multithreaded kernel selection: with crossOriginIsolated (sites serving COOP/COEP) it uses mt (-pthread, PASS1
|
|
12
|
+
// layers in parallel — measured 2.2x), otherwise st (zero-config). The dynamic import makes bundlers emit both as chunks,
|
|
13
|
+
// but only one is loaded at runtime. On mt init failure (SAB blocked, …) it falls back to st.
|
|
12
14
|
const loadCore = async () => {
|
|
13
15
|
const isolated = typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated
|
|
14
16
|
if (isolated) {
|
|
@@ -16,7 +18,7 @@ const loadCore = async () => {
|
|
|
16
18
|
const M = await (await import('./slicer_core.mt.js')).default()
|
|
17
19
|
console.info('[slicer.worker] core: mt (threads)')
|
|
18
20
|
return M
|
|
19
|
-
} catch (e) { console.warn('[slicer.worker] mt
|
|
21
|
+
} catch (e) { console.warn('[slicer.worker] mt load failed — falling back to st:', e) }
|
|
20
22
|
}
|
|
21
23
|
const M = await (await import('./slicer_core.js')).default()
|
|
22
24
|
console.info('[slicer.worker] core: st')
|
|
@@ -30,33 +32,34 @@ self.onmessage = async (e) => {
|
|
|
30
32
|
try {
|
|
31
33
|
if (!modPromise) modPromise = loadCore()
|
|
32
34
|
const Module = await modPromise
|
|
33
|
-
//
|
|
35
|
+
// Warmup: only load the kernel (+ spawn the mt pthread pool) ahead of time — removes the perceived load on the first slice
|
|
34
36
|
if (d.cmd === 'warmup') { self.postMessage({ type: 'warm' }); return }
|
|
35
|
-
// 20
|
|
37
|
+
// Stage 20: manual support painting — selector state persists in this worker Module (slicing uses the same Module).
|
|
36
38
|
if (d.cmd === 'prepare') { Module.selector_prepare(new Uint8Array(d.stl)); self.postMessage({ type: 'prepared', facets: Module.selector_facet_count() }); return }
|
|
37
39
|
if (d.cmd === 'paint') { Module.selector_paint(d.facet, d.hx, d.hy, d.hz, d.cx, d.cy, d.cz, d.radius, d.enforcer);
|
|
38
40
|
self.postMessage({ type: 'painted', enf: Module.selector_painted_count(true), blk: Module.selector_painted_count(false) }); return }
|
|
39
41
|
if (d.cmd === 'clear') { Module.selector_clear(); self.postMessage({ type: 'painted', enf: 0, blk: 0 }); return }
|
|
40
42
|
if (d.cmd === 'overlay') { self.postMessage({ type: 'overlay', enf: Array.from(Module.selector_overlay(true)), blk: Array.from(Module.selector_overlay(false)) }); return }
|
|
41
43
|
|
|
42
|
-
if (d.stall) return // 30
|
|
43
|
-
// mt(SAB):
|
|
44
|
-
// UI
|
|
44
|
+
if (d.stall) return // stage-30 test hook: simulate a hang -> verifies the main-thread watchdog fires (not set in production)
|
|
45
|
+
// mt (SAB): share the address of the real support progress counter (u32) with main once — even while the kernel is
|
|
46
|
+
// blocked inside C++, the UI thread polls the SAB directly to show support progress. st (non-shared buffer) is unsupported -> not sent.
|
|
45
47
|
if (!self.__supSabSent) {
|
|
46
48
|
self.__supSabSent = true
|
|
47
49
|
try {
|
|
48
|
-
// Module.HEAPU8
|
|
50
|
+
// Module.HEAPU8 is not exposed by the emscripten 6.x glue — obtain the buffer via embind typed_memory_view
|
|
49
51
|
const v = Module.sup_progress_view && Module.sup_progress_view()
|
|
52
|
+
const c = Module.cancel_flag_view && Module.cancel_flag_view()
|
|
50
53
|
if (v && v.buffer instanceof SharedArrayBuffer)
|
|
51
|
-
self.postMessage({ type: 'supsab', buf: v.buffer, ptr: v.byteOffset })
|
|
54
|
+
self.postMessage({ type: 'supsab', buf: v.buffer, ptr: v.byteOffset, cancelPtr: c ? c.byteOffset : 0 })
|
|
52
55
|
} catch {}
|
|
53
56
|
}
|
|
54
|
-
//
|
|
55
|
-
//
|
|
57
|
+
// Default: slice. Register the layer sink -> the kernel calls back per layer (z, idx, gcodeChunk, pathsF32, widthsF32).
|
|
58
|
+
// Each layer is transferred to main immediately (toolpath buffers moved) -> the worker copy is freed -> heap headroom before the next layer.
|
|
56
59
|
const onProgress = (done, total) => self.postMessage({ type: 'progress', done, total })
|
|
57
60
|
Module.set_layer_sink((z, idx, gcode, paths, widths) => {
|
|
58
61
|
const transfer = []
|
|
59
|
-
if (paths && paths.buffer) transfer.push(paths.buffer) //
|
|
62
|
+
if (paths && paths.buffer) transfer.push(paths.buffer) // economy mode yields empty arrays (no .buffer) -> nothing to transfer
|
|
60
63
|
if (widths && widths.buffer) transfer.push(widths.buffer)
|
|
61
64
|
self.postMessage({ type: 'layer', z, idx, gcode, paths, widths }, transfer)
|
|
62
65
|
})
|
|
@@ -64,10 +67,10 @@ self.onmessage = async (e) => {
|
|
|
64
67
|
try { r = Module.slice(new Uint8Array(d.stl), d.params, onProgress) }
|
|
65
68
|
finally { Module.clear_layer_sink() }
|
|
66
69
|
if (r && r.error) { self.postMessage({ type: 'error', error: String(r.error) }); return }
|
|
67
|
-
// streamed=true
|
|
70
|
+
// streamed=true -> g-code/layers were already emitted as 'layer' (result holds stats only). batch/MM keep them in result.
|
|
68
71
|
self.postMessage({ type: 'done', result: r })
|
|
69
72
|
} catch (err) {
|
|
70
|
-
// WASM abort("memory access out of bounds")
|
|
73
|
+
// Includes the WASM abort("memory access out of bounds") — the main thread's OOM ladder decides on re-creation / economy retry.
|
|
71
74
|
self.postMessage({ type: 'error', error: String((err && err.message) || err) })
|
|
72
75
|
}
|
|
73
76
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
// 25
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
import toggles from '
|
|
6
|
-
import schema from 'three-slicer/data/config-schema.json'
|
|
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'
|
|
7
6
|
|
|
8
|
-
//
|
|
7
|
+
// Settings value accessor (settings map first, schema default otherwise). Percentages ("15%") become numbers.
|
|
9
8
|
export function makeCfg(settings) {
|
|
10
9
|
const val = k => {
|
|
11
10
|
let v = settings ? settings[k] : undefined
|
|
@@ -21,7 +20,7 @@ export function makeCfg(settings) {
|
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
//
|
|
23
|
+
// Upstream locals translated inline (viewer-related conditions only). Each value is a boolean.
|
|
25
24
|
function makeLocals(cfg) {
|
|
26
25
|
const have_raft = cfg.int('raft_layers') > 0
|
|
27
26
|
const has_spiral_vase = cfg.bool('spiral_mode')
|
|
@@ -42,24 +41,24 @@ function makeLocals(cfg) {
|
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
// enable_if(
|
|
44
|
+
// Evaluates enable_if (up to the first comma) to a boolean. Returns null (= stays enabled) when not fully translatable.
|
|
46
45
|
export function evalEnableIf(expr, locals, cfg) {
|
|
47
46
|
let s = String(expr).split(',')[0].trim()
|
|
48
47
|
if (!s) return null
|
|
49
|
-
//
|
|
48
|
+
// Direct config access pattern -> value substitution
|
|
50
49
|
s = s.replace(/config->opt_bool\(\s*"([^"]+)"\s*\)/g, (_, k) => cfg.bool(k) ? 'true' : 'false')
|
|
51
50
|
s = s.replace(/config->opt_int\(\s*"([^"]+)"\s*\)/g, (_, k) => String(cfg.int(k)))
|
|
52
51
|
s = s.replace(/config->opt_float\(\s*"([^"]+)"\s*\)/g, (_, k) => String(cfg.float(k)))
|
|
53
|
-
//
|
|
52
|
+
// Substitute known locals (longest name first — prevents partial matches)
|
|
54
53
|
for (const name of Object.keys(locals).sort((a, b) => b.length - a.length))
|
|
55
54
|
s = s.replace(new RegExp('\\b' + name + '\\b', 'g'), locals[name] ? 'true' : 'false')
|
|
56
|
-
//
|
|
55
|
+
// Check the remaining string is a safe boolean expression (only true/false/numbers/operators/parens) — otherwise unknown -> null
|
|
57
56
|
const stripped = s.replace(/\btrue\b|\bfalse\b/g, '1')
|
|
58
57
|
if (!/^[\s()!<>=&|0-9.]*$/.test(stripped)) return null
|
|
59
58
|
try { return !!Function('"use strict";return (' + s + ')')() } catch { return null }
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
//
|
|
61
|
+
// Map of keys disabled under the current settings { key: condition }. Only rules that are unambiguously false.
|
|
63
62
|
export function disabledKeys(cfg) {
|
|
64
63
|
const locals = makeLocals(cfg)
|
|
65
64
|
const groups = ['toggle_print_fff_options', 'toggle_filament_options', 'toggle_printer_options',
|
package/package.json
CHANGED
|
@@ -1,55 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "three-slicer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Browser/WASM 3D-printing slicer reverse-engineered from OrcaSlicer: slicing engine (Arachne walls, tree/grid supports, multi-material), React viewer with GPU toolpath preview, and schema-driven settings UI.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./engine/index.d.ts",
|
|
10
|
-
"
|
|
10
|
+
"default": "./engine/index.js"
|
|
11
11
|
},
|
|
12
|
-
"./settings":
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"./
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"./
|
|
21
|
-
|
|
12
|
+
"./settings": {
|
|
13
|
+
"types": "./types/settings.d.ts",
|
|
14
|
+
"default": "./engine/src/settings.js"
|
|
15
|
+
},
|
|
16
|
+
"./toggle": {
|
|
17
|
+
"types": "./types/toggle.d.ts",
|
|
18
|
+
"default": "./engine/src/toggle_eval.js"
|
|
19
|
+
},
|
|
20
|
+
"./worker": {
|
|
21
|
+
"types": "./types/worker.d.ts",
|
|
22
|
+
"default": "./engine/src/slicer.worker.js"
|
|
23
|
+
},
|
|
24
|
+
"./wasm": {
|
|
25
|
+
"types": "./types/wasm.d.ts",
|
|
26
|
+
"default": "./engine/src/slicer_core.js"
|
|
27
|
+
},
|
|
28
|
+
"./wasm-mt": {
|
|
29
|
+
"types": "./types/wasm.d.ts",
|
|
30
|
+
"default": "./engine/src/slicer_core.mt.js"
|
|
31
|
+
},
|
|
32
|
+
"./viewer": {
|
|
33
|
+
"types": "./types/viewer.d.ts",
|
|
34
|
+
"default": "./viewer/dist/Viewport.js"
|
|
35
|
+
},
|
|
36
|
+
"./viewer/toolpath": {
|
|
37
|
+
"types": "./types/viewer-toolpath.d.ts",
|
|
38
|
+
"default": "./viewer/dist/toolpath_gpu.js"
|
|
39
|
+
},
|
|
40
|
+
"./viewer/loaders": {
|
|
41
|
+
"types": "./types/viewer-loaders.d.ts",
|
|
42
|
+
"default": "./viewer/dist/model_loaders.js"
|
|
43
|
+
},
|
|
44
|
+
"./components": {
|
|
45
|
+
"types": "./types/components.d.ts",
|
|
46
|
+
"default": "./components/dist/SettingsPanel.js"
|
|
47
|
+
},
|
|
48
|
+
"./data": {
|
|
49
|
+
"types": "./types/data.d.ts",
|
|
50
|
+
"default": "./engine/src/data.js"
|
|
51
|
+
},
|
|
52
|
+
"./data/*": {
|
|
53
|
+
"types": "./types/data/*.d.ts",
|
|
54
|
+
"default": "./data/*"
|
|
55
|
+
}
|
|
22
56
|
},
|
|
23
57
|
"scripts": {
|
|
24
|
-
"build": "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"
|
|
58
|
+
"build": "node types/gen_settings_types.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",
|
|
59
|
+
"check": "bash pack_check.sh"
|
|
25
60
|
},
|
|
26
61
|
"peerDependencies": {
|
|
27
62
|
"react": ">=18",
|
|
28
63
|
"react-dom": ">=18",
|
|
29
64
|
"three": "^0.160.0"
|
|
30
65
|
},
|
|
31
|
-
"peerDependenciesMeta": {
|
|
32
|
-
"react": {
|
|
33
|
-
"optional": true
|
|
34
|
-
},
|
|
35
|
-
"react-dom": {
|
|
36
|
-
"optional": true
|
|
37
|
-
},
|
|
38
|
-
"three": {
|
|
39
|
-
"optional": true
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
66
|
"devDependencies": {
|
|
67
|
+
"typescript": "^5.6.0",
|
|
43
68
|
"vite": "^5.4.11"
|
|
44
69
|
},
|
|
45
70
|
"files": [
|
|
46
71
|
"engine",
|
|
72
|
+
"types",
|
|
47
73
|
"components/dist",
|
|
48
74
|
"viewer/dist",
|
|
49
|
-
"viewer/src",
|
|
50
75
|
"viewer/styles.css",
|
|
51
76
|
"components/styles.css",
|
|
52
|
-
"data
|
|
77
|
+
"data/config-schema.json",
|
|
78
|
+
"data/ui-tree.json",
|
|
79
|
+
"data/toggle-rules.json",
|
|
80
|
+
"data/invalidation-map.json",
|
|
53
81
|
"README.md",
|
|
54
82
|
"LICENSE.txt"
|
|
55
83
|
],
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
|
|
18
|
+
declare const SettingsPanel: React.FC<SettingsPanelProps>
|
|
19
|
+
export default SettingsPanel
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// three-slicer/data/config-schema.json — the 907 option definitions extracted from upstream OrcaSlicer.
|
|
2
|
+
// ponytail: values are not narrowed to literals. Inferring 907 keys x 33 fields as literals crawls in consumer tsc.
|
|
3
|
+
// Use SlicerSettings (three-slicer/settings) when you need per-key accuracy.
|
|
4
|
+
|
|
5
|
+
/** The coXxx type tag from the upstream ConfigOptionDef */
|
|
6
|
+
export type ConfigOptionType =
|
|
7
|
+
| 'coBool' | 'coBools'
|
|
8
|
+
| 'coInt' | 'coInts'
|
|
9
|
+
| 'coFloat' | 'coFloats'
|
|
10
|
+
| 'coPercent' | 'coPercents'
|
|
11
|
+
| 'coFloatOrPercent' | 'coFloatsOrPercents'
|
|
12
|
+
| 'coString' | 'coStrings'
|
|
13
|
+
| 'coEnum' | 'coEnums'
|
|
14
|
+
| 'coPoint' | 'coPoints' | 'coPointsGroups'
|
|
15
|
+
|
|
16
|
+
/** Of the 907 keys, only `type` and `defined_in` always exist. Everything else is optional. */
|
|
17
|
+
export interface ConfigOption {
|
|
18
|
+
type: ConfigOptionType
|
|
19
|
+
/** Name of the source function it was extracted from (e.g. 'init_common_params') */
|
|
20
|
+
defined_in: string
|
|
21
|
+
line?: number
|
|
22
|
+
mode?: string
|
|
23
|
+
label?: string
|
|
24
|
+
full_label?: string
|
|
25
|
+
tooltip?: string
|
|
26
|
+
sidetext?: string
|
|
27
|
+
category?: string
|
|
28
|
+
default?: unknown
|
|
29
|
+
default_type?: string
|
|
30
|
+
default_raw?: string
|
|
31
|
+
enum_type?: string
|
|
32
|
+
enum_values?: string[]
|
|
33
|
+
enum_labels?: string[]
|
|
34
|
+
enum_values_copied_from?: string
|
|
35
|
+
enum_labels_copied_from?: string
|
|
36
|
+
min?: number
|
|
37
|
+
max?: number
|
|
38
|
+
max_literal?: unknown
|
|
39
|
+
gui_type?: string
|
|
40
|
+
gui_flags?: string
|
|
41
|
+
ratio_over?: string
|
|
42
|
+
nullable?: boolean
|
|
43
|
+
readonly?: boolean
|
|
44
|
+
multiline?: boolean
|
|
45
|
+
full_width?: boolean
|
|
46
|
+
height?: number
|
|
47
|
+
cli?: string
|
|
48
|
+
cli_params?: string
|
|
49
|
+
aliases?: string[]
|
|
50
|
+
plugin_type?: string
|
|
51
|
+
generated_by_loop?: unknown
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare const schema: Record<string, ConfigOption>
|
|
55
|
+
export default schema
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// three-slicer/data/invalidation-map.json — setting key change -> invalidated slice steps.
|
|
2
|
+
// Top-level key = the kind of invalidation target (print/object/filament, etc.).
|
|
3
|
+
|
|
4
|
+
export interface InvalidationRule {
|
|
5
|
+
/** The config-schema keys this rule reacts to */
|
|
6
|
+
keys: string[]
|
|
7
|
+
/** The invalidated steps (e.g. 'psSkirtBrim', 'posSlice') */
|
|
8
|
+
steps: string[]
|
|
9
|
+
/** Special handling that cannot be expressed as a step */
|
|
10
|
+
special: string[]
|
|
11
|
+
line?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare const invalidationMap: Record<string, InvalidationRule[]>
|
|
15
|
+
export default invalidationMap
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// three-slicer/data/toggle-rules.json — enable_if expressions extracted from upstream toggle_options().
|
|
2
|
+
// Top-level key = group name (e.g. 'toggle_print_fff_options', 'TabPrinter::toggle_options').
|
|
3
|
+
|
|
4
|
+
export interface ToggleRule {
|
|
5
|
+
/** The raw C++ expression string. Evaluated by evalEnableIf in three-slicer/toggle. */
|
|
6
|
+
enable_if?: string
|
|
7
|
+
/** The config-schema keys governed by this condition */
|
|
8
|
+
fields?: string[]
|
|
9
|
+
line?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ToggleGroup {
|
|
13
|
+
/** Local variable name -> the upstream C++ expression (string). Translation is hardcoded in toggle_eval.js. */
|
|
14
|
+
locals: Record<string, string>
|
|
15
|
+
rules: ToggleRule[]
|
|
16
|
+
stats?: { toggle_calls_in_source: number; rules_extracted: number }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare const toggles: Record<string, ToggleGroup>
|
|
20
|
+
export default toggles
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// three-slicer/data/ui-tree.json — the page/group tree from upstream Tab.cpp.
|
|
2
|
+
// Top-level key = builder name (e.g. 'TabPrint::build'), value = the pages that builder creates.
|
|
3
|
+
|
|
4
|
+
export interface UIGroup {
|
|
5
|
+
group: string
|
|
6
|
+
line?: number
|
|
7
|
+
/** The config-schema option keys */
|
|
8
|
+
options: string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UIPage {
|
|
12
|
+
page: string
|
|
13
|
+
icon?: string
|
|
14
|
+
line?: number
|
|
15
|
+
groups: UIGroup[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare const uiTree: Record<string, UIPage[]>
|
|
19
|
+
export default uiTree
|
package/types/data.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// three-slicer/data — the 4 extracted JSON files. Prefer this over importing the raw JSON:
|
|
2
|
+
// the import attribute (`with { type: 'json' }`) is already applied, so it works in Node and bundlers alike.
|
|
3
|
+
import type { ConfigOption } from './data/config-schema.json.d.ts'
|
|
4
|
+
import type { UIPage } from './data/ui-tree.json.d.ts'
|
|
5
|
+
import type { ToggleGroup } from './data/toggle-rules.json.d.ts'
|
|
6
|
+
import type { InvalidationRule } from './data/invalidation-map.json.d.ts'
|
|
7
|
+
|
|
8
|
+
export type { ConfigOption, UIPage, ToggleGroup, InvalidationRule }
|
|
9
|
+
|
|
10
|
+
/** The 907 option definitions */
|
|
11
|
+
export const schema: Record<string, ConfigOption>
|
|
12
|
+
/** Builder name -> page tree */
|
|
13
|
+
export const uiTree: Record<string, UIPage[]>
|
|
14
|
+
/** Group name -> enable_if rules */
|
|
15
|
+
export const toggleRules: Record<string, ToggleGroup>
|
|
16
|
+
/** Setting key change -> invalidated steps */
|
|
17
|
+
export const invalidationMap: Record<string, InvalidationRule[]>
|