three-slicer 0.1.1 → 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.
@@ -1,40 +1,40 @@
1
- // 우측 설정 패널(편집 가능)의 실제 값 → 커널 슬라이스 파라미터 매핑.
2
- // - 설정 상태는 sparse 맵(key→value): 편집한 키만 저장, 없으면 config-schema default.
3
- // - 벡터형(coFloats/coInts 등)은 첫 원소만 사용/편집(단순화).
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
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,34 +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()
50
52
  const c = Module.cancel_flag_view && Module.cancel_flag_view()
51
53
  if (v && v.buffer instanceof SharedArrayBuffer)
52
54
  self.postMessage({ type: 'supsab', buf: v.buffer, ptr: v.byteOffset, cancelPtr: c ? c.byteOffset : 0 })
53
55
  } catch {}
54
56
  }
55
- // 기본: 슬라이스. 레이어 싱크 등록 → 커널이 레이어마다 콜백(z, idx, gcodeChunk, pathsF32, widthsF32).
56
- // 각 레이어를 즉시 메인으로 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.
57
59
  const onProgress = (done, total) => self.postMessage({ type: 'progress', done, total })
58
60
  Module.set_layer_sink((z, idx, gcode, paths, widths) => {
59
61
  const transfer = []
60
- 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
61
63
  if (widths && widths.buffer) transfer.push(widths.buffer)
62
64
  self.postMessage({ type: 'layer', z, idx, gcode, paths, widths }, transfer)
63
65
  })
@@ -65,10 +67,10 @@ self.onmessage = async (e) => {
65
67
  try { r = Module.slice(new Uint8Array(d.stl), d.params, onProgress) }
66
68
  finally { Module.clear_layer_sink() }
67
69
  if (r && r.error) { self.postMessage({ type: 'error', error: String(r.error) }); return }
68
- // 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.
69
71
  self.postMessage({ type: 'done', result: r })
70
72
  } catch (err) {
71
- // 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.
72
74
  self.postMessage({ type: 'error', error: String((err && err.message) || err) })
73
75
  }
74
76
  }
@@ -1,10 +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.
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
5
  import { toggleRules as toggles, schema } from './data.js'
6
6
 
7
- // 설정값 접근기 (settings 맵 우선, 없으면 스키마 default). 퍼센트("15%")는 숫자로.
7
+ // Settings value accessor (settings map first, schema default otherwise). Percentages ("15%") become numbers.
8
8
  export function makeCfg(settings) {
9
9
  const val = k => {
10
10
  let v = settings ? settings[k] : undefined
@@ -20,7 +20,7 @@ export function makeCfg(settings) {
20
20
  }
21
21
  }
22
22
 
23
- // 원본 locals 를 인라인 번역(뷰어 관련 조건만). 각 값은 boolean.
23
+ // Upstream locals translated inline (viewer-related conditions only). Each value is a boolean.
24
24
  function makeLocals(cfg) {
25
25
  const have_raft = cfg.int('raft_layers') > 0
26
26
  const has_spiral_vase = cfg.bool('spiral_mode')
@@ -41,24 +41,24 @@ function makeLocals(cfg) {
41
41
  }
42
42
  }
43
43
 
44
- // enable_if(첫 콤마부) 를 boolean 으로 평가. 완전 번역 불가하면 null(=활성 유지).
44
+ // Evaluates enable_if (up to the first comma) to a boolean. Returns null (= stays enabled) when not fully translatable.
45
45
  export function evalEnableIf(expr, locals, cfg) {
46
46
  let s = String(expr).split(',')[0].trim()
47
47
  if (!s) return null
48
- // 직접 config 접근 패턴 → 값 치환
48
+ // Direct config access pattern -> value substitution
49
49
  s = s.replace(/config->opt_bool\(\s*"([^"]+)"\s*\)/g, (_, k) => cfg.bool(k) ? 'true' : 'false')
50
50
  s = s.replace(/config->opt_int\(\s*"([^"]+)"\s*\)/g, (_, k) => String(cfg.int(k)))
51
51
  s = s.replace(/config->opt_float\(\s*"([^"]+)"\s*\)/g, (_, k) => String(cfg.float(k)))
52
- // 알려진 로컬 치환(긴 이름 먼저 — 부분매칭 방지)
52
+ // Substitute known locals (longest name first — prevents partial matches)
53
53
  for (const name of Object.keys(locals).sort((a, b) => b.length - a.length))
54
54
  s = s.replace(new RegExp('\\b' + name + '\\b', 'g'), locals[name] ? 'true' : 'false')
55
- // 남은 문자열이 안전한 boolean 식인지(true/false/숫자/연산자/괄호만) — 아니면 미지 → null
55
+ // Check the remaining string is a safe boolean expression (only true/false/numbers/operators/parens) — otherwise unknown -> null
56
56
  const stripped = s.replace(/\btrue\b|\bfalse\b/g, '1')
57
57
  if (!/^[\s()!<>=&|0-9.]*$/.test(stripped)) return null
58
58
  try { return !!Function('"use strict";return (' + s + ')')() } catch { return null }
59
59
  }
60
60
 
61
- // 현재 설정에서 비활성 키 맵 { key: 조건식 }. 조건이 명확히 false 인 규칙만.
61
+ // Map of keys disabled under the current settings { key: condition }. Only rules that are unambiguously false.
62
62
  export function disabledKeys(cfg) {
63
63
  const locals = makeLocals(cfg)
64
64
  const groups = ['toggle_print_fff_options', 'toggle_filament_options', 'toggle_printer_options',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-slicer",
3
- "version": "0.1.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",
@@ -3,15 +3,15 @@ import type * as React from 'react'
3
3
  import type { SlicerSettings } from './settings-keys.d.ts'
4
4
 
5
5
  export interface SettingsPanelProps {
6
- /** sparse 맵 — 편집한 키만. 없는 키는 config-schema default 가 쓰인다. */
6
+ /** Sparse map — only edited keys. Missing keys fall back to the config-schema default. */
7
7
  settings: SlicerSettings
8
- /** React setState 형태. 상태가 컴포넌트 밖으로 나가는 유일한 통로. */
8
+ /** React setState shape. The only channel through which state leaves the component. */
9
9
  setSettings: React.Dispatch<React.SetStateAction<SlicerSettings>>
10
- /** 라벨 클릭 — 딥링크/상세용. 생략하면 평범한 라벨(라우터 결합 0). */
10
+ /** Label click — for deep links / detail views. Omit for a plain label (zero router coupling). */
11
11
  onOptionOpen?: (optKey: string) => void
12
- /** 다른 패널 안에 끼워 넣을 때 */
12
+ /** When embedding inside another panel */
13
13
  embedded?: boolean
14
- /** 스키마 구동 위젯을 키 단위로 교체 (예: `{ printable_area: MyBedEditor }`) */
14
+ /** Replace schema-driven widgets per key (e.g. `{ printable_area: MyBedEditor }`) */
15
15
  customWidgets?: Record<string, React.ComponentType<any>>
16
16
  }
17
17
 
@@ -1,8 +1,8 @@
1
- // three-slicer/data/config-schema.json — OrcaSlicer 원본에서 추출한 907개 옵션 정의.
2
- // ponytail: 값을 리터럴로 좁히지 않는다. 907키 × 33필드를 리터럴로 추론시키면 소비자 tsc 가 기어간다.
3
- // 키 단위 정확도가 필요하면 SlicerSettings(three-slicer/settings) 쪽을 쓸 것.
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
4
 
5
- /** 원본 ConfigOptionDef 의 coXxx 타입 태그 */
5
+ /** The coXxx type tag from the upstream ConfigOptionDef */
6
6
  export type ConfigOptionType =
7
7
  | 'coBool' | 'coBools'
8
8
  | 'coInt' | 'coInts'
@@ -13,10 +13,10 @@ export type ConfigOptionType =
13
13
  | 'coEnum' | 'coEnums'
14
14
  | 'coPoint' | 'coPoints' | 'coPointsGroups'
15
15
 
16
- /** 907키 중 `type` 과 `defined_in` 만 전부 존재한다. 나머지는 전부 optional. */
16
+ /** Of the 907 keys, only `type` and `defined_in` always exist. Everything else is optional. */
17
17
  export interface ConfigOption {
18
18
  type: ConfigOptionType
19
- /** 추출된 원본 함수명 (예: 'init_common_params') */
19
+ /** Name of the source function it was extracted from (e.g. 'init_common_params') */
20
20
  defined_in: string
21
21
  line?: number
22
22
  mode?: string
@@ -1,12 +1,12 @@
1
- // three-slicer/data/invalidation-map.json — 설정 키 변경 → 무효화되는 슬라이스 단계.
2
- // 최상위 키 = 무효화 대상 종류(print/object/filament 등).
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
3
 
4
4
  export interface InvalidationRule {
5
- /** 이 규칙이 반응하는 config-schema 키들 */
5
+ /** The config-schema keys this rule reacts to */
6
6
  keys: string[]
7
- /** 무효화되는 단계 (예: 'psSkirtBrim', 'posSlice') */
7
+ /** The invalidated steps (e.g. 'psSkirtBrim', 'posSlice') */
8
8
  steps: string[]
9
- /** 단계로 표현 안 되는 특수 처리 */
9
+ /** Special handling that cannot be expressed as a step */
10
10
  special: string[]
11
11
  line?: number
12
12
  }
@@ -1,16 +1,16 @@
1
- // three-slicer/data/toggle-rules.json — 원본 toggle_options() 의 enable_if 조건식 추출.
2
- // 최상위 키 = 그룹명(예: 'toggle_print_fff_options', 'TabPrinter::toggle_options').
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
3
 
4
4
  export interface ToggleRule {
5
- /** 원본 C++ 조건식 문자열. 평가는 three-slicer/toggle 의 evalEnableIf. */
5
+ /** The raw C++ expression string. Evaluated by evalEnableIf in three-slicer/toggle. */
6
6
  enable_if?: string
7
- /** 이 조건이 지배하는 config-schema 키들 */
7
+ /** The config-schema keys governed by this condition */
8
8
  fields?: string[]
9
9
  line?: number
10
10
  }
11
11
 
12
12
  export interface ToggleGroup {
13
- /** 로컬 변수명 → 원본 C++ 표현식(문자열). 번역은 toggle_eval.js 가 하드코딩으로 한다. */
13
+ /** Local variable name -> the upstream C++ expression (string). Translation is hardcoded in toggle_eval.js. */
14
14
  locals: Record<string, string>
15
15
  rules: ToggleRule[]
16
16
  stats?: { toggle_calls_in_source: number; rules_extracted: number }
@@ -1,10 +1,10 @@
1
- // three-slicer/data/ui-tree.json — 원본 Tab.cpp 의 페이지/그룹 트리.
2
- // 최상위 키 = 빌더명(예: 'TabPrint::build'), 값 = 그 빌더가 만드는 페이지들.
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
3
 
4
4
  export interface UIGroup {
5
5
  group: string
6
6
  line?: number
7
- /** config-schema 의 옵션 키들 */
7
+ /** The config-schema option keys */
8
8
  options: string[]
9
9
  }
10
10
 
package/types/data.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- // three-slicer/data — 추출 JSON 4종. 원본 JSON 을 직접 import 하는 것보다 이쪽을 권한다:
2
- // import attribute(`with { type: 'json' }`)가 이미 붙어 있어 Node·번들러 양쪽에서 그대로 돌아간다.
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
3
  import type { ConfigOption } from './data/config-schema.json.d.ts'
4
4
  import type { UIPage } from './data/ui-tree.json.d.ts'
5
5
  import type { ToggleGroup } from './data/toggle-rules.json.d.ts'
@@ -7,11 +7,11 @@ import type { InvalidationRule } from './data/invalidation-map.json.d.ts'
7
7
 
8
8
  export type { ConfigOption, UIPage, ToggleGroup, InvalidationRule }
9
9
 
10
- /** 907개 옵션 정의 */
10
+ /** The 907 option definitions */
11
11
  export const schema: Record<string, ConfigOption>
12
- /** 빌더명 → 페이지 트리 */
12
+ /** Builder name -> page tree */
13
13
  export const uiTree: Record<string, UIPage[]>
14
- /** 그룹명 → enable_if 규칙 */
14
+ /** Group name -> enable_if rules */
15
15
  export const toggleRules: Record<string, ToggleGroup>
16
- /** 설정 키 변경 → 무효화 단계 */
16
+ /** Setting key change -> invalidated steps */
17
17
  export const invalidationMap: Record<string, InvalidationRule[]>
@@ -1,8 +1,8 @@
1
- // config-schema.json → types/settings-keys.d.ts (SlicerSettings 인터페이스, 907키).
2
- // 재생성: node types/gen_settings_types.mjs (수동 편집 금지 — 이 스크립트만 고칠 것)
1
+ // config-schema.json -> types/settings-keys.d.ts (the SlicerSettings interface, 907 keys).
2
+ // Regenerate: node types/gen_settings_types.mjs (do not hand-edit — fix this script instead)
3
3
  //
4
- // ponytail: 스키마의 coXxx 타입만 보고 매핑한다. min/max·nullable 은 타입에 반영하지 않음
5
- // (런타임 검증이지 타입 검증이 아니고, 907키 × branded number 는 tsc 를 느리게 만든다).
4
+ // ponytail: mapping is driven purely by the schema's coXxx type. min/max and nullable are not reflected in types
5
+ // (they are runtime validation, not type validation, and 907 keys x branded number slows tsc down).
6
6
  import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
7
7
  import { dirname, resolve } from 'node:path'
8
8
  import { fileURLToPath } from 'node:url'
@@ -11,12 +11,12 @@ const here = dirname(fileURLToPath(import.meta.url))
11
11
  const schema = JSON.parse(readFileSync(resolve(here, '../data/config-schema.json'), 'utf8'))
12
12
 
13
13
  const lit = (v) => "'" + String(v).replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'"
14
- // enum_values 가 없는 coEnum 도 있다(70/64 불일치) → string 으로 fail-open.
14
+ // Some coEnum entries have no enum_values (70/64 mismatch) -> fail open to string.
15
15
  const enumOf = (o) => (Array.isArray(o.enum_values) && o.enum_values.length)
16
16
  ? o.enum_values.map(lit).join(' | ')
17
17
  : 'string'
18
18
 
19
- // 벡터형은 `T[] | T` — settingScalar() 가 둘 다 받고, UI 는 첫 원소만 편집한다(settings.js:9).
19
+ // Vector types are `T[] | T` — settingScalar() accepts both, and the UI edits only the first element (settings.js:9).
20
20
  const vec = (t) => `${/\|/.test(t) ? `(${t})` : t}[] | ${/\|/.test(t) ? `(${t})` : t}`
21
21
 
22
22
  function tsType(o) {
@@ -53,18 +53,18 @@ for (const key of Object.keys(schema).sort()) {
53
53
  writeFileSync(resolve(here, 'settings-keys.d.ts'), `// AUTO-GENERATED by types/gen_settings_types.mjs — DO NOT EDIT.
54
54
  // Source: data/config-schema.json (${Object.keys(schema).length} keys)
55
55
 
56
- /** [x, y] — coPoint 계열 */
56
+ /** [x, y] — coPoint family */
57
57
  export type Point = [number, number]
58
58
 
59
59
  /**
60
- * 슬라이서 설정 맵. sparse — 편집한 키만 담고, 없는 키는 config-schema 의 default 가 쓰인다.
61
- * 임의 키 확장이 필요하면 \`SlicerSettings & Record<string, unknown>\` 로 넓혀서 쓸 것.
60
+ * Slicer settings map. Sparse — it holds only edited keys; missing keys use the config-schema default.
61
+ * If you need arbitrary key extension, widen it with \`SlicerSettings & Record<string, unknown>\`.
62
62
  */
63
63
  export interface SlicerSettings {
64
64
  ${lines.join('\n')}
65
65
  }
66
66
 
67
- /** config-schema 에 존재하는 설정 키 */
67
+ /** Setting keys that exist in config-schema */
68
68
  export type SettingKey = keyof SlicerSettings & string
69
69
  `)
70
70
  console.log(`settings-keys.d.ts written — ${Object.keys(schema).length} keys`)
@@ -1,12 +1,12 @@
1
1
  // AUTO-GENERATED by types/gen_settings_types.mjs — DO NOT EDIT.
2
2
  // Source: data/config-schema.json (907 keys)
3
3
 
4
- /** [x, y] — coPoint 계열 */
4
+ /** [x, y] — coPoint family */
5
5
  export type Point = [number, number]
6
6
 
7
7
  /**
8
- * 슬라이서 설정 맵. sparse — 편집한 키만 담고, 없는 키는 config-schema 의 default 가 쓰인다.
9
- * 임의 키 확장이 필요하면 `SlicerSettings & Record<string, unknown>` 로 넓혀서 쓸 것.
8
+ * Slicer settings map. Sparse — it holds only edited keys; missing keys use the config-schema default.
9
+ * If you need arbitrary key extension, widen it with `SlicerSettings & Record<string, unknown>`.
10
10
  */
11
11
  export interface SlicerSettings {
12
12
  absolute_correction?: number
@@ -1700,5 +1700,5 @@ export interface SlicerSettings {
1700
1700
  zhop?: number
1701
1701
  }
1702
1702
 
1703
- /** config-schema 에 존재하는 설정 키 */
1703
+ /** Setting keys that exist in config-schema */
1704
1704
  export type SettingKey = keyof SlicerSettings & string
@@ -2,18 +2,18 @@
2
2
  import type { SlicerSettings, SettingKey, Point } from './settings-keys.d.ts'
3
3
  export type { SlicerSettings, SettingKey, Point }
4
4
 
5
- /** config-schema 의 default 값 (없는 키면 undefined) */
5
+ /** The config-schema default value (undefined for unknown keys) */
6
6
  export function schemaDefault(key: string): unknown
7
7
 
8
- /** settings 맵 우선, 없으면 스키마 default. 벡터형은 벡터 그대로 돌려준다. */
8
+ /** Settings map first, schema default otherwise. Vector types are returned as-is. */
9
9
  export function settingRaw(settings: SlicerSettings | null | undefined, key: string): unknown
10
10
 
11
- /** settingRaw 를 스칼라로 정규화 — 벡터면 [0]. */
11
+ /** Normalizes settingRaw to a scalar — [0] for vectors. */
12
12
  export function settingScalar(settings: SlicerSettings | null | undefined, key: string): unknown
13
13
 
14
14
  /**
15
- * 설정 맵 → 커널 슬라이스 파라미터. 스키마 키에서 유도하며 누락 키는 내부 기본값으로 채운다.
16
- * ponytail: 반환 키가 53개라 개별 나열 대신 Record — 커널 파라미터는 slice() 에 그대로 넘기는 용도지
17
- * 소비자가 필드 단위로 읽는 API 가 아니다. 필드 접근이 필요해지면 그때 펼칠 것.
15
+ * Settings map -> kernel slice params. Derived from schema keys; missing keys are filled with internal defaults.
16
+ * ponytail: 53 returned keys, so Record instead of listing each — kernel params are passed straight to slice(),
17
+ * not an API consumers read field by field. Expand it when field access is actually needed.
18
18
  */
19
19
  export function deriveKernelParams(settings: SlicerSettings | null | undefined): Record<string, unknown>
package/types/toggle.d.ts CHANGED
@@ -1,22 +1,22 @@
1
- // three-slicer/toggle — toggle-rules.json 의 enable_if 조건식 부분 평가.
1
+ // three-slicer/toggle — partial evaluation of the enable_if expressions in toggle-rules.json.
2
2
  import type { SlicerSettings } from './settings-keys.d.ts'
3
3
 
4
- /** 설정 접근기 — 값이 없으면 스키마 default, 벡터형은 [0]. */
4
+ /** Settings accessor — schema default when a value is missing, [0] for vector types. */
5
5
  export interface ToggleCfg {
6
6
  bool(key: string): boolean
7
7
  int(key: string): number
8
8
  float(key: string): number
9
- /** 키가 config-schema 에 존재하는가 */
9
+ /** Whether the key exists in config-schema */
10
10
  has(key: string): boolean
11
11
  }
12
12
 
13
13
  export function makeCfg(settings: SlicerSettings | null | undefined): ToggleCfg
14
14
 
15
15
  /**
16
- * enable_if 조건식 1개 평가. 번역 불가한 식은 `null`(= fail-open, 활성 유지) 을 돌려준다 —
17
- * `false` 와 반드시 구분해서 다룰 것.
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
18
  */
19
19
  export function evalEnableIf(expr: string, locals: Record<string, unknown>, cfg: ToggleCfg): boolean | null
20
20
 
21
- /** 현재 설정에서 비활성이어야 하는 키 → 그렇게 만든 enable_if 조건식. 조건이 명확히 false 인 규칙만. */
21
+ /** Keys that must be disabled under the current settings -> the enable_if expression that disabled them. Only rules that are unambiguously false. */
22
22
  export function disabledKeys(cfg: ToggleCfg): Record<string, string>