three-slicer 0.1.1 → 0.1.3

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 (39) hide show
  1. package/components/dist/SettingsPanel.js +112 -112
  2. package/components/styles.css +9 -9
  3. package/data/config-schema.json +999 -900
  4. package/data/invalidation-map.json +6 -5
  5. package/data/printers.json +1 -0
  6. package/data/processes.js +2 -0
  7. package/data/toggle-rules.json +44 -44
  8. package/data/ui-tree.json +170 -494
  9. package/engine/examples/headless.mjs +2 -2
  10. package/engine/index.js +2 -2
  11. package/engine/src/data.js +13 -7
  12. package/engine/src/settings.js +120 -35
  13. package/engine/src/slicer.worker.js +23 -21
  14. package/engine/src/slicer_core.js +0 -0
  15. package/engine/src/slicer_core.mt.js +0 -0
  16. package/engine/src/toggle_eval.js +11 -11
  17. package/package.json +4 -2
  18. package/types/components.d.ts +5 -5
  19. package/types/data/config-schema.json.d.ts +6 -6
  20. package/types/data/invalidation-map.json.d.ts +5 -5
  21. package/types/data/printers.json.d.ts +23 -0
  22. package/types/data/processes.js.d.ts +21 -0
  23. package/types/data/toggle-rules.json.d.ts +5 -5
  24. package/types/data/ui-tree.json.d.ts +3 -3
  25. package/types/data.d.ts +10 -7
  26. package/types/gen_settings_types.mjs +10 -10
  27. package/types/settings-keys.d.ts +18 -7
  28. package/types/settings.d.ts +6 -6
  29. package/types/toggle.d.ts +6 -6
  30. package/types/viewer-loaders.d.ts +8 -8
  31. package/types/viewer-toolpath.d.ts +17 -17
  32. package/types/viewer.d.ts +3 -3
  33. package/types/wasm.d.ts +3 -3
  34. package/types/worker.d.ts +2 -2
  35. package/viewer/dist/Viewport.js +1760 -966
  36. package/viewer/dist/make_worker.js +6 -6
  37. package/viewer/dist/model_loaders.js +35 -35
  38. package/viewer/dist/toolpath_gpu.js +146 -147
  39. package/viewer/styles.css +46 -25
@@ -1,7 +1,7 @@
1
1
  // three-slicer headless SDK example + independence proof: drive the engine from Node using ONLY the
2
2
  // public API (no UI, no viewer). Generates a 20mm cube STL, slices it, prints stats + G-code length.
3
3
  // run: node packages/engine/examples/headless.mjs
4
- import { createSlicer } from 'three-slicer' // 워크스페이스 심링크로 해석 — 외부 소비자와 동일한 경로
4
+ import { createSlicer } from 'three-slicer' // resolved via the workspace symlink — same path an external consumer takes
5
5
 
6
6
  // minimal binary-STL writer for a box (public API takes a binary-STL ArrayBuffer)
7
7
  function boxTris(ox, oy, oz, sx, sy, sz) {
@@ -27,7 +27,7 @@ const slicer = await createSlicer()
27
27
  const r = slicer.slice(cube, params)
28
28
  console.log(`batch : layers=${r.stats.layers} segments=${r.stats.path_segments} filament=${r.stats.filament_mm.toFixed(1)}mm gcode=${r.gcode.length} chars`)
29
29
 
30
- // (b) streaming slice via onLayer (30단계) — assemble G-code from per-layer chunks
30
+ // (b) streaming slice via onLayer (stage 30) — assemble G-code from per-layer chunks
31
31
  let chunks = 0, gbytes = 0
32
32
  const rs = slicer.slice(cube, params, { onLayer: ({ gcode }) => { chunks++; gbytes += gcode.length } })
33
33
  console.log(`stream: layers=${chunks} streamed=${rs.stats.streamed} assembled-gcode=${gbytes} chars`)
package/engine/index.js CHANGED
@@ -7,7 +7,7 @@ import createModule from './src/slicer_core.js'
7
7
  const u8 = (b) => (b instanceof Uint8Array ? b : new Uint8Array(b))
8
8
 
9
9
  // Off-main-thread worker URL for the browser: `new Worker(engineWorkerURL(), { type: 'module' })`.
10
- // The worker speaks the 30단계 streaming protocol ({type:'layer'|'done'|'error'|'progress'}).
10
+ // The worker speaks the stage-30 streaming protocol ({type:'layer'|'done'|'error'|'progress'}).
11
11
  export const engineWorkerURL = () => new URL('./src/slicer.worker.js', import.meta.url)
12
12
 
13
13
  // createSlicer(): load the WASM kernel and return a handle. Works in Node and the browser main thread.
@@ -15,7 +15,7 @@ export const engineWorkerURL = () => new URL('./src/slicer.worker.js', import.me
15
15
  // - stl: ArrayBuffer|Uint8Array (binary STL)
16
16
  // - params: kernel-params object OR JSON string (see web/GUIDE.md; use deriveKernelParams to build from UI settings)
17
17
  // - onProgress(done,total): per-layer progress
18
- // - onLayer({z,idx,gcode,paths,widths}): 30단계 streaming — emits + frees each layer; when set, the
18
+ // - onLayer({z,idx,gcode,paths,widths}): stage-30 streaming — emits + frees each layer; when set, the
19
19
  // returned result carries stats only (assemble gcode/layers from the callbacks). When omitted, the
20
20
  // result carries the whole { gcode, stats, layers }.
21
21
  // paintPrepare/paint/paintClear/overlay: manual support enforcer/blocker painting (TriangleSelector).
@@ -1,12 +1,18 @@
1
- // 추출 JSON 4종의 유일한 import 지점.
1
+ // The single import site for the extracted JSON files.
2
2
  //
3
- // 왜 한 군데로 모으는가: JSON 은 Node 22+ ESM 에서 `with { type: 'json' }` 이 필수인데,
4
- // Vite/esbuild 는 번들 출력에서 이 attribute 를 떼어낸다(build.target·importAttributesKey 로
5
- // 막을 수 없음 — 실측). 그래서 같은 JSON 을 "attribute 있는 원본(engine/src/*.js)" 과
6
- // "attribute 없는 번들(components/dist)" 이 동시에 가리키면 소비자 번들러가
7
- // "inconsistent import attributes" 로 경고한다. 여기 한 곳만 JSON 을 잡고 나머지는
8
- // 이 모듈을 external 로 import → 번들에 JSON import 자체가 남지 않아 불일치가 성립하지 않는다.
3
+ // Why funnel them through one module: JSON requires `with { type: 'json' }` in Node 22+ ESM, but
4
+ // Vite/esbuild strip that attribute from bundle output (neither build.target nor importAttributesKey
5
+ // prevents it — measured). So if the same JSON is pointed at by both "the original with the attribute
6
+ // (engine/src/*.js)" and "the bundle without it (components/dist)", the consumer's bundler warns with
7
+ // "inconsistent import attributes". Only this file holds the JSON; everything else imports this module
8
+ // as external -> no JSON import survives in the bundle, so the mismatch cannot arise.
9
9
  export { default as schema } from 'three-slicer/data/config-schema.json' with { type: 'json' }
10
10
  export { default as uiTree } from 'three-slicer/data/ui-tree.json' with { type: 'json' }
11
11
  export { default as toggleRules } from 'three-slicer/data/toggle-rules.json' with { type: 'json' }
12
12
  export { default as invalidationMap } from 'three-slicer/data/invalidation-map.json' with { type: 'json' }
13
+ export { default as printers } from 'three-slicer/data/printers.json' with { type: 'json' }
14
+ // processes is the largest artifact (~800 KB) and is only needed once a printer is picked, so it stays a dynamic
15
+ // import. It is generated as a JS module rather than JSON precisely because of the attribute problem above, in its
16
+ // other direction: a dynamic JSON import needs the attribute in Node, and that same attribute makes browsers
17
+ // reject the text/javascript response a dev server gives for .json.
18
+ export const loadProcesses = () => import('three-slicer/data/processes.js').then(m => m.default)
@@ -1,40 +1,116 @@
1
- // 우측 설정 패널(편집 가능)의 실제 값 → 커널 슬라이스 파라미터 매핑.
2
- // - 설정 상태는 sparse 맵(key→value): 편집한 키만 저장, 없으면 config-schema default.
3
- // - 벡터형(coFloats/coInts 등)은 첫 원소만 사용/편집(단순화).
4
- import { schema } from './data.js'
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, printers, loadProcesses } 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
+ // Machine limits (the "Motion ability" printer page) -> kernel time-estimate parameters.
17
+ // Declarative on purpose: the kernel collapses X/Y into one axis, so the mapping cannot be a plain pass-through,
18
+ // but adding a limit stays a data row instead of code. The fallback is the kernel's own default, so an unedited
19
+ // profile produces exactly the previous estimate. Upstream leaves machine_max_*_x/y/z/e without a schema default
20
+ // (they only ever come from a printer preset), hence the explicit fallbacks here.
21
+ const MACHINE_LIMITS = {
22
+ machine_max_speed_xy: ['machine_max_speed_x', 500],
23
+ machine_max_speed_z: ['machine_max_speed_z', 12],
24
+ machine_max_speed_e: ['machine_max_speed_e', 30],
25
+ machine_max_accel_xy: ['machine_max_acceleration_x', 5000],
26
+ machine_max_accel_z: ['machine_max_acceleration_z', 500],
27
+ machine_max_accel_e: ['machine_max_acceleration_e', 5000],
28
+ machine_jerk_xy: ['machine_max_jerk_x', 9],
29
+ machine_jerk_z: ['machine_max_jerk_z', 0.4],
30
+ machine_jerk_e: ['machine_max_jerk_e', 2.5],
31
+ machine_accel_print: ['default_acceleration', 5000],
32
+ machine_accel_travel: ['travel_acceleration', 5000],
33
+ machine_accel_retract: ['machine_max_acceleration_retracting', 5000],
34
+ }
35
+ // The schema keys the machine limits are read from — for UI that has to show "which printer characteristics are in play"
36
+ // without hardcoding key strings of its own.
37
+ export const machineLimitKeys = Object.values(MACHINE_LIMITS).map(([key]) => key)
38
+
39
+ // ---- Printer profiles -------------------------------------------------------
40
+ // printers.json is stored column-oriented (see its .d.ts). These two hide that layout so no consumer decodes it.
41
+
42
+ /** Every option key a printer profile can set — what to clear before applying a different printer. */
43
+ export const printerKeys = printers.keys
44
+
45
+ /** Vendor -> profile name -> `[nozzle, setIndex, model]`, straight from the data (for building a picker). */
46
+ export const printersByVendor = printers.byVendor
47
+
48
+ // Process (print) presets live in the ~800 KB processes.json, so they load on demand — the first call fetches,
49
+ // later ones reuse the same promise. Returns a small facade so no caller has to know the column layout.
50
+ let processesPromise = null
51
+ export function processPresets() {
52
+ processesPromise ??= loadProcesses().then(data => ({
53
+ /** Every key a process preset can set — clear these before applying a different one */
54
+ keys: data.keys,
55
+ /** Preset names compatible with a printer profile, in upstream order */
56
+ listFor: (printerProfileName) =>
57
+ (data.byPrinter[printerProfileName] ?? []).map(i => data.presets[i][0]),
58
+ /** The settings a preset applies, ready to merge. `null` when unknown. */
59
+ settingsFor: (presetName) => {
60
+ const preset = data.presets.find(([name]) => name === presetName)
61
+ if (!preset) return null
62
+ const row = data.sets[preset[1]], out = {}
63
+ data.keys.forEach((key, i) => { if (row[i] != null) out[key] = row[i] })
64
+ return out
65
+ },
66
+ }))
67
+ return processesPromise
68
+ }
69
+
70
+ function printerEntry(profileName) {
71
+ for (const models of Object.values(printers.byVendor)) {
72
+ const entry = models[profileName]
73
+ if (entry) return entry
74
+ }
75
+ return null
76
+ }
77
+
78
+ /** The settings a printer profile applies, ready to merge into the settings map. `null` when unknown. */
79
+ export function printerSettings(profileName) {
80
+ const entry = printerEntry(profileName)
81
+ if (!entry) return null
82
+ const row = printers.sets[entry[1]]
83
+ const out = {}
84
+ printers.keys.forEach((key, i) => { if (row[i] != null) out[key] = row[i] })
85
+ return out
86
+ }
87
+
88
+ /** The vendor's recommended process preset for a printer, or '' when the profile names none. */
89
+ export function printerDefaultPreset(profileName) { return printerEntry(profileName)?.[3] ?? '' }
90
+
91
+
92
+ // Right-panel settings values -> kernel parameters (derived from schema keys)
17
93
  export function deriveKernelParams(settings) {
18
94
  const S = k => settingScalar(settings, k)
19
95
  const num = (k, d) => { const v = Number(S(k)); return Number.isFinite(v) ? v : d }
20
96
  const bool = (k, d) => { const v = settingRaw(settings, k); return typeof v === 'boolean' ? v : (Array.isArray(v) ? !!v[0] : (v == null ? d : !!v)) }
21
97
 
22
- let line_width = num('line_width', 0); if (!line_width) line_width = 0.42 // 0=auto → 기본 0.42
98
+ let line_width = num('line_width', 0); if (!line_width) line_width = 0.42 // 0=auto -> default 0.42
23
99
 
24
100
  let pat = String(S('sparse_infill_pattern') ?? 'rectilinear')
25
- if (!KERNEL_PATTERNS.includes(pat)) pat = 'rectilinear' // 커널 미지원 패턴 → rectilinear
101
+ if (!KERNEL_PATTERNS.includes(pat)) pat = 'rectilinear' // pattern unsupported by the kernel -> rectilinear
26
102
 
27
103
  let seam = String(S('seam_position') ?? 'back')
28
104
  if (seam === 'aligned_back') seam = 'back'
29
105
  if (!KERNEL_SEAMS.includes(seam)) seam = 'back'
30
106
 
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.
107
+ // Support style: schema enum (default/grid/snug/organic/tree_slim/tree_strong/tree_hybrid)
108
+ // -> kernel grid|tree|tree_lite. Since stage 18 the tree/organic family maps to the real organic TreeSupport ('tree',
109
+ // generate_tree_support_3D -> real type5 branch toolpaths); everything else maps to grid.
34
110
  const styleRaw = String(S('support_style') ?? 'default')
35
111
  const support_style = /tree|organic/i.test(styleRaw) ? 'tree' : 'grid'
36
112
 
37
- // 베드: printable_area 첫 사각형의 바운딩박스
113
+ // Bed: bounding box of the first rectangle in printable_area
38
114
  const pa = settingRaw(settings, 'printable_area')
39
115
  let bed_width = 256, bed_depth = 256
40
116
  if (Array.isArray(pa) && pa.length) {
@@ -42,15 +118,19 @@ export function deriveKernelParams(settings) {
42
118
  bed_width = Math.max(...xs) - Math.min(...xs); bed_depth = Math.max(...ys) - Math.min(...ys)
43
119
  }
44
120
 
45
- // 21단계: 피처별 폭 — 원본(문자열 "120%" 포함) 그대로 커널로 전달. 미편집 키는 생략 → 커널이 auto(=line_width) 유도.
46
- // 0=자동 시맨틱은 커널 resolve_lw(원본 Flow 산식)이 처리. 기본값 불변(모든 피처 미설정 → line_width).
121
+ // Stage 21: per-feature widths — passed to the kernel verbatim (including strings like "120%"). Unedited keys are omitted -> the kernel derives auto(=line_width).
122
+ // The 0=auto semantics are handled by the kernel's resolve_lw (upstream Flow formula). Defaults unchanged (no feature set -> line_width).
47
123
  const widths = {}
48
124
  for (const k of ['outer_wall_line_width','inner_wall_line_width','top_surface_line_width',
49
125
  'sparse_infill_line_width','internal_solid_infill_line_width','initial_layer_line_width']) {
50
126
  const v = S(k); if (v != null && v !== '') widths[k] = v
51
127
  }
128
+ const machine = {}
129
+ for (const [param, [key, fallback]] of Object.entries(MACHINE_LIMITS)) machine[param] = num(key, fallback)
130
+
52
131
  return {
53
132
  ...widths,
133
+ ...machine,
54
134
  layer_height: num('layer_height', 0.2),
55
135
  first_layer_height: num('initial_layer_print_height', 0.2),
56
136
  line_width,
@@ -63,12 +143,12 @@ export function deriveKernelParams(settings) {
63
143
  seam_position: seam,
64
144
  skirt_loops: num('skirt_loops', 1),
65
145
  skirt_distance: num('skirt_distance', 2),
66
- skirt_height: num('skirt_height', 1), // 33단계: 커널이 첫 레이어 고정이었음
146
+ skirt_height: num('skirt_height', 1), // stage 33: the kernel used to hardcode the first layer
67
147
  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단계: 트리 서포트 경로 단순화 허용오차
148
+ brim_object_gap: num('brim_object_gap', 0), // stage 33: the kernel used to hardcode w*0.5
149
+ retract_length: num('retraction_length', 0.8), // vector[0]
150
+ retraction_minimum_travel: num('retraction_minimum_travel', 2), // stage 33: used to be the kernel constant 2.0
151
+ gcode_resolution: num('resolution', 0.01), // stage 33: tree-support path simplification tolerance
72
152
  retract_speed: num('retraction_speed', 30),
73
153
  z_hop: num('z_hop', 0.4),
74
154
  travel_speed: num('travel_speed', 120),
@@ -78,15 +158,20 @@ export function deriveKernelParams(settings) {
78
158
  filament_diameter: num('filament_diameter', 1.75),
79
159
  flow_ratio: num('filament_flow_ratio', 1.0),
80
160
  nozzle_temp: num('nozzle_temperature', 200),
81
- bed_temp: num('hot_plate_temp', 45), // bed_temperature 키 없음 → hot_plate_temp
161
+ bed_temp: num('hot_plate_temp', 45), // no bed_temperature key -> hot_plate_temp
82
162
  bed_width, bed_depth,
163
+ bed_height: num('printable_height', 0), // 0 = profile states no ceiling -> kernel skips the check
164
+ // Explicitly-set only: the schema default here is upstream's generic "G28 / G1 Z5" preamble, and falling back
165
+ // to it would rewrite the emitted G-code for every caller. The kernel keeps its own preamble when these are empty.
166
+ machine_start_gcode: String(settings?.machine_start_gcode ?? ''),
167
+ machine_end_gcode: String(settings?.machine_end_gcode ?? ''),
83
168
  enable_support: bool('enable_support', false),
84
169
  support_threshold_angle: num('support_threshold_angle', 30),
85
170
  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=현행 등가)
171
+ 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
172
  support_xy_distance: num('support_object_xy_distance', 0.35),
88
173
  support_interface_top_layers: num('support_interface_top_layers', 2),
89
- // 33단계: 커널 하드코딩 제거로 신설된 서포트 키 배선(원본 스키마 키 그대로)
174
+ // Stage 33: support keys added when the kernel hardcoding was removed (upstream schema key names kept)
90
175
  support_angle: num('support_angle', 0),
91
176
  support_base_pattern: String(S('support_base_pattern') ?? 'default'),
92
177
  support_interface_pattern: String(S('support_interface_pattern') ?? 'auto'),
@@ -97,34 +182,34 @@ export function deriveKernelParams(settings) {
97
182
  support_expansion: num('support_expansion', 0),
98
183
  support_threshold_overlap: (() => { const v = settingRaw(settings, 'support_threshold_overlap')
99
184
  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 (압출폭 비율)
185
+ const n = Number(v); return Number.isFinite(n) ? n : 0.5 })(), // "50%" -> 0.5 (ratio of extrusion width)
101
186
  support_on_build_plate_only: bool('support_on_build_plate_only', false),
102
187
  support_interface_bottom_layers: num('support_interface_bottom_layers', 0),
103
188
  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
189
+ raft_expansion: num('raft_expansion', 1.5), // stage 33: the kernel used to hardcode +3.0
190
+ raft_contact_distance: num('raft_contact_distance', 0.1), // stage 33: previously ignored by the kernel
191
+ fan_speed: num('fan_max_speed', 100), // no fan_speed key -> fan_max_speed
107
192
  close_fan_the_first_x_layers: num('close_fan_the_first_x_layers', 1),
108
193
  full_fan_speed_layer: num('full_fan_speed_layer', 0),
109
194
  slow_down_layer_time: num('slow_down_layer_time', 5),
110
195
  enable_arc_fitting: bool('enable_arc_fitting', false),
111
196
  spiral_mode: bool('spiral_mode', false),
112
- // 5단계 신규 (스키마 대응 키에서 유도)
113
- seam_slope_type: String(S('seam_slope_type') ?? 'none'), // none|external|all → scarf 심
197
+ // New in stage 5 (derived from the matching schema keys)
198
+ seam_slope_type: String(S('seam_slope_type') ?? 'none'), // none|external|all -> scarf seam
114
199
  enable_pressure_advance: bool('enable_pressure_advance', false),
115
200
  pressure_advance: num('pressure_advance', 0.02),
116
- support_style, // grid|tree_lite (위에서 매핑)
201
+ support_style, // grid|tree_lite (mapped above)
117
202
  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)
203
+ // New in stage 6 (derived from the matching schema keys). The MM parameters (extruder_count/mm_group_split) are
204
+ // injected at onSlice time from the viewer's per-object extruder assignment (not included here).
205
+ ironing_type: String(S('ironing_type') ?? 'no ironing'), // no ironing|top|topmost|solid (the top family = on)
121
206
  ironing_spacing: num('ironing_spacing', 0.1),
122
207
  ironing_flow: num('ironing_flow', 10),
123
208
  ironing_speed: num('ironing_speed', 20),
124
209
  reduce_crossing_wall: bool('reduce_crossing_wall', false),
125
210
  max_volumetric_extrusion_rate_slope: num('max_volumetric_extrusion_rate_slope', 0),
126
- // 7단계: 벽 생성기 classic|arachne (arachne = 이식된 실제 OrcaSlicer WallToolPaths, 가변폭)
211
+ // Stage 7: wall generator classic|arachne (arachne = the real ported OrcaSlicer WallToolPaths, variable width)
127
212
  wall_generator: (String(S('wall_generator') ?? 'classic') === 'arachne') ? 'arachne' : 'classic',
128
- // support_density 는 스키마에 대응 키가 없어 커널 기본(0.15), scarf_length 도 커널 기본(10mm) 사용
213
+ // support_density has no matching schema key, so the kernel default (0.15) is used; scarf_length likewise uses the kernel default (10mm)
129
214
  }
130
215
  }
@@ -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
  }
Binary file
Binary file
@@ -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.3",
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",
@@ -78,6 +78,8 @@
78
78
  "data/ui-tree.json",
79
79
  "data/toggle-rules.json",
80
80
  "data/invalidation-map.json",
81
+ "data/printers.json",
82
+ "data/processes.js",
81
83
  "README.md",
82
84
  "LICENSE.txt"
83
85
  ],
@@ -86,7 +88,7 @@
86
88
  "url": "git+https://github.com/kimgh06/Web_Three_Slicer.git",
87
89
  "directory": "packages"
88
90
  },
89
- "homepage": "https://github.com/kimgh06/Web_Three_Slicer#readme",
91
+ "homepage": "https://slicer.kimgh06.com",
90
92
  "keywords": [
91
93
  "3d-printing",
92
94
  "slicer",
@@ -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
  }
@@ -0,0 +1,23 @@
1
+ // printers.json — per-printer settings extracted from OrcaSlicer's resources/profiles/<Vendor>/machine/.
2
+ // Column layout: `keys` names the columns once and each entry in `sets` is a positional row, because
3
+ // repeating the key names per printer costs more than all the values together. Rows are deduplicated —
4
+ // a printer's nozzle variants usually share one row — so a printer holds an index rather than its own copy.
5
+ // Prefer `printerSettings()` from three-slicer/settings over decoding this by hand.
6
+
7
+ /** A positional row aligned to `keys`; `null` where the profile chain never set that option. */
8
+ export type PrinterRow = (unknown | null)[]
9
+
10
+ /** `[nozzle diameter, index into sets, model name without the nozzle suffix]` */
11
+ export type PrinterEntry = [string, number, string]
12
+
13
+ export interface PrinterData {
14
+ /** Option keys, in column order — the same keys the settings panel edits */
15
+ keys: string[]
16
+ /** Deduplicated value rows */
17
+ sets: PrinterRow[]
18
+ /** Vendor -> printer profile name -> entry */
19
+ byVendor: Record<string, Record<string, PrinterEntry>>
20
+ }
21
+
22
+ declare const printers: PrinterData
23
+ export default printers