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.
Files changed (64) hide show
  1. package/README.md +342 -24
  2. package/components/README.md +1 -1
  3. package/components/dist/SettingsPanel.js +111 -112
  4. package/components/styles.css +9 -9
  5. package/data/invalidation-map.json +1 -1
  6. package/engine/examples/headless.mjs +2 -2
  7. package/engine/index.d.ts +3 -1
  8. package/engine/index.js +2 -2
  9. package/engine/src/data.js +12 -0
  10. package/engine/src/settings.js +35 -35
  11. package/engine/src/slicer.worker.js +25 -22
  12. package/engine/src/slicer_core.js +0 -0
  13. package/engine/src/slicer_core.mt.js +0 -0
  14. package/engine/src/toggle_eval.js +12 -13
  15. package/package.json +54 -26
  16. package/types/components.d.ts +19 -0
  17. package/types/data/config-schema.json.d.ts +55 -0
  18. package/types/data/invalidation-map.json.d.ts +15 -0
  19. package/types/data/toggle-rules.json.d.ts +20 -0
  20. package/types/data/ui-tree.json.d.ts +19 -0
  21. package/types/data.d.ts +17 -0
  22. package/types/gen_settings_types.mjs +70 -0
  23. package/types/settings-keys.d.ts +1704 -0
  24. package/types/settings.d.ts +19 -0
  25. package/types/toggle.d.ts +22 -0
  26. package/types/viewer-loaders.d.ts +33 -0
  27. package/types/viewer-toolpath.d.ts +105 -0
  28. package/types/viewer.d.ts +15 -0
  29. package/types/wasm.d.ts +5 -0
  30. package/types/worker.d.ts +3 -0
  31. package/viewer/README.md +1 -1
  32. package/viewer/dist/Viewport.js +1774 -923
  33. package/viewer/dist/make_worker.js +6 -6
  34. package/viewer/dist/model_loaders.js +221 -109
  35. package/viewer/dist/toolpath_gpu.js +146 -147
  36. package/viewer/styles.css +27 -25
  37. package/data/config-schema-builddump.json +0 -12921
  38. package/viewer/src/Viewport.jsx +0 -1353
  39. package/viewer/src/assets/add.svg +0 -1
  40. package/viewer/src/assets/arrange.svg +0 -1
  41. package/viewer/src/assets/boolean.svg +0 -1
  42. package/viewer/src/assets/cut.svg +0 -1
  43. package/viewer/src/assets/delete.svg +0 -1
  44. package/viewer/src/assets/deleteall.svg +0 -1
  45. package/viewer/src/assets/duplicate.svg +0 -1
  46. package/viewer/src/assets/measure.svg +0 -1
  47. package/viewer/src/assets/mmu.svg +0 -1
  48. package/viewer/src/assets/move.svg +0 -1
  49. package/viewer/src/assets/negative.svg +0 -1
  50. package/viewer/src/assets/onbed.svg +0 -1
  51. package/viewer/src/assets/open.svg +0 -1
  52. package/viewer/src/assets/orient.svg +0 -1
  53. package/viewer/src/assets/paint.svg +0 -1
  54. package/viewer/src/assets/rotate.svg +0 -1
  55. package/viewer/src/assets/scale.svg +0 -1
  56. package/viewer/src/assets/seam.svg +0 -1
  57. package/viewer/src/assets/split.svg +0 -1
  58. package/viewer/src/assets/textmark.svg +0 -1
  59. package/viewer/src/assets/varlayer.svg +0 -1
  60. package/viewer/src/icons.js +0 -23
  61. package/viewer/src/make_worker.js +0 -9
  62. package/viewer/src/model_loaders.js +0 -178
  63. package/viewer/src/shadow_host.jsx +0 -30
  64. package/viewer/src/toolpath_gpu.js +0 -430
@@ -1,40 +1,40 @@
1
- // 우측 설정 패널(편집 가능)의 실제 값 → 커널 슬라이스 파라미터 매핑.
2
- // - 설정 상태는 sparse 맵(key→value): 편집한 키만 저장, 없으면 config-schema default.
3
- // - 벡터형(coFloats/coInts 등)은 첫 원소만 사용/편집(단순화).
4
- import schema from 'three-slicer/data/config-schema.json'
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
- // 스칼라(벡터면 [0]) 로 정규화
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단계: 이식된 실제 Fill 패턴(gyroid TPMS/honeycomb/3dhoneycomb/crosshatch/concentric) + 기존 근사 패턴
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 → 기본 0.42
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' // 커널 미지원 패턴 → 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
- // 서포트 스타일: 스키마 enum(default/grid/snug/organic/tree_slim/tree_strong/tree_hybrid)
32
- // → 커널 grid|tree|tree_lite. 18단계부터 tree/organic 계열은 실 오가닉 TreeSupport('tree',
33
- // generate_tree_support_3D → 실 type5 브랜치 툴패스)로 매핑, 그 외 grid.
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
- // 베드: printable_area 첫 사각형의 바운딩박스
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단계: 피처별 폭 — 원본(문자열 "120%" 포함) 그대로 커널로 전달. 미편집 키는 생략 → 커널이 auto(=line_width) 유도.
46
- // 0=자동 시맨틱은 커널 resolve_lw(원본 Flow 산식)이 처리. 기본값 불변(모든 피처 미설정 → line_width).
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단계: 커널이 w*0.5 고정이었음
69
- retract_length: num('retraction_length', 0.8), // 벡터[0]
70
- retraction_minimum_travel: num('retraction_minimum_travel', 2), // 33단계: 커널 상수 2.0 이었음
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 키 없음 → hot_plate_temp
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단계: 서포트 바닥 z-gap(기본 0.2=현행 등가)
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%" → 0.5 (압출폭 비율)
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단계: 커널이 +3.0 고정이었음
105
- raft_contact_distance: num('raft_contact_distance', 0.1), // 33단계: 커널에 미반영이었음
106
- fan_speed: num('fan_max_speed', 100), // fan_speed 키 없음 → fan_max_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 → scarf 심
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단계 신규 (스키마 대응 키에서 유도). MM 파라미터(extruder_count/mm_group_split)는
119
- // 뷰어 오브젝트별 익스트루더 지정에서 onSlice 시 주입(여기선 미포함).
120
- ironing_type: String(S('ironing_type') ?? 'no ironing'), // no ironing|top|topmost|solid (top류=on)
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단계: 벽 생성기 classic|arachne (arachne = 이식된 실제 OrcaSlicer WallToolPaths, 가변폭)
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 는 스키마에 대응 키가 없어 커널 기본(0.15), scarf_length 도 커널 기본(10mm) 사용
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
- // 슬라이스를 메인 스레드 밖에서 실행 (UI 논블록) + 30단계 레이어 스트리밍.
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 이라 slicer_core.js 안에 wasm 이 인라인 → worker 에서도 외부 fetch 없음.
3
+ // SINGLE_FILE means the wasm is inlined into slicer_core.js -> no external fetch from the worker either.
4
4
  //
5
- // 30단계(OOM 내성): set_layer_sink 로 커널이 레이어를 만드는 대로 방출받아 즉시 메인으로 transfer 하고
6
- // (Float32Array 버퍼 이전 → worker 사본 즉시 해제) 커널은 그 레이어 버퍼를 힙에서 해제한다. 결과 상주
7
- // (전체 gw.s + 전체 layersArr)가 사라져 WASM 힙 피크가 급감 → 대형 모델 OOM 회피. 최종 'done' 은 stats 만.
8
- // 절약 모드(params.economy)면 툴패스 없이 g-code 청크만 방출(프리뷰 없이 완주). MM/실PE 경로는 배치 폴백.
9
- // 멀티스레드 커널 자동 선택: crossOriginIsolated(COOP/COEP 제공 사이트)면 mt(-pthread, PASS1 레이어
10
- // 병렬 — 실측 2.2×), 아니면 st(zero-config). 동적 import 라 번들러는 둘 다 청크로 내지만 런타임엔
11
- // 하나만 로드. mt 초기화 실패(SAB 차단 등) 시 st 폴백.
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 로드 실패 — st 폴백:', e) }
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
- // 워밍업: 커널 로드(+mt pthread 풀 스폰)만 미리 수행 — 첫 슬라이스의 체감 로딩 제거
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단계: 수동 서포트 페인팅 — selector 상태는 이 worker Module 에 지속(슬라이스도 같은 Module).
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): 서포트 실진행 카운터(u32) 주소를 메인에 1회 공유 — 커널이 C++ 안에 블록된 동안에도
44
- // UI 스레드가 SAB 를 직접 폴링해 서포트 진행률을 표시한다. st(비공유 버퍼)는 미지원 → 미전송.
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 은 emscripten 6.x 글루가 노출하지 않음 — embind typed_memory_view 로 버퍼 획득
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
- // 기본: 슬라이스. 레이어 싱크 등록 → 커널이 레이어마다 콜백(z, idx, gcodeChunk, pathsF32, widthsF32).
55
- // 각 레이어를 즉시 메인으로 transfer(툴패스 버퍼 이전) → worker 사본 해제 → 다음 레이어 전 힙 여유.
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) // 절약 모드는 빈 배열(.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 → g-code/layers 는 'layer' 로 이미 방출됨(result 엔 stats 만). batch/MM 는 result 에 그대로.
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") 포함 — 메인의 OOM 사다리가 재생성/절약 재시도 판정.
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단계 S5.1: toggle-rules 부분 번역 — 원본 C++ toggle_options 조건식(enable_if) → JS.
2
- // 범위: 뷰어가 다루는 키에 관련된 규칙 중, 아래 하드코딩 로컬로 "완전히" 번역 가능한 것만 적용.
3
- // 번역 불가(모르는 로컬/enum 비교 등)는 fail-open(활성 유지) — 잘못된 비활성 방지(정직한 부분 구현).
4
- // 전체 907키·231규칙 완역은 범위 외(README 기록). 참조: toggle-rules.json 의 toggle_print_fff_options.locals.
5
- import toggles from 'three-slicer/data/toggle-rules.json'
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
- // 설정값 접근기 (settings 맵 우선, 없으면 스키마 default). 퍼센트("15%")는 숫자로.
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
- // 원본 locals 를 인라인 번역(뷰어 관련 조건만). 각 값은 boolean.
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(첫 콤마부) 를 boolean 으로 평가. 완전 번역 불가하면 null(=활성 유지).
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
- // 직접 config 접근 패턴 → 값 치환
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
- // 남은 문자열이 안전한 boolean 식인지(true/false/숫자/연산자/괄호만) — 아니면 미지 → null
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
- // 현재 설정에서 비활성 키 맵 { key: 조건식 }. 조건이 명확히 false 인 규칙만.
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.0",
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
- "import": "./engine/index.js"
10
+ "default": "./engine/index.js"
11
11
  },
12
- "./settings": "./engine/src/settings.js",
13
- "./toggle": "./engine/src/toggle_eval.js",
14
- "./worker": "./engine/src/slicer.worker.js",
15
- "./wasm": "./engine/src/slicer_core.js",
16
- "./wasm-mt": "./engine/src/slicer_core.mt.js",
17
- "./viewer": "./viewer/dist/Viewport.js",
18
- "./viewer/toolpath": "./viewer/dist/toolpath_gpu.js",
19
- "./viewer/loaders": "./viewer/dist/model_loaders.js",
20
- "./components": "./components/dist/SettingsPanel.js",
21
- "./data/*": "./data/*"
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/*.json",
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
@@ -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[]>