three-slicer-viewer 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-slicer-viewer",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "The three-slicer viewer without the slicer — model loading, G-code parsing, GPU toolpath rendering and the settings transforms, under MIT. Plug a slicer in through the `slicer` prop.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,28 @@
1
+ // Printer coordinates. Kept apart from settings_core.js because it needs no schema: core/plate_settings.js reads it
2
+ // without loading the config schema behind it.
3
+ //
4
+ // The viewer and the kernel work in a plate frame centred on the bed; the printer's own coordinates start wherever
5
+ // printable_area does. The kernel adds the bed centre to every coordinate it writes (Params::bed_center_x/y), and
6
+ // every stored printer coordinate (wipe_tower_x/y, a 3mf object position, an imported G-code) converts through the
7
+ // same two functions. `params` is deriveKernelParams' output (bed_origin_x/y are present only when the corner is
8
+ // not (0,0)). Most beds start at (0,0), where the centre is half the bed; a delta bed is centred on (0,0) itself
9
+ // (Anycubic Predator: -185..185).
10
+ import { BED_FALLBACK } from './kernel_defaults.js'
11
+
12
+ export function bedOrigin(params) {
13
+ return { x: params?.bed_origin_x ?? 0, y: params?.bed_origin_y ?? 0 }
14
+ }
15
+ export function bedCenter(params) {
16
+ const origin = bedOrigin(params)
17
+ return { x: origin.x + (params?.bed_width ?? BED_FALLBACK.width) / 2, y: origin.y + (params?.bed_depth ?? BED_FALLBACK.depth) / 2 }
18
+ }
19
+
20
+ // A printable_area that is not an axis-aligned rectangle (a delta bed's circle as 72 points, a cut-corner bed), as
21
+ // plate-local points: printer coordinates minus the bed centre. null for a rectangle, which bed_width/bed_depth
22
+ // already describe exactly.
23
+ export function bedShapeLocal(area, center) {
24
+ if (!Array.isArray(area) || area.length < 3) return null
25
+ const xs = new Set(area.map(point => point[0])), ys = new Set(area.map(point => point[1]))
26
+ if (area.length === 4 && xs.size === 2 && ys.size === 2) return null
27
+ return area.map(([x, y]) => [x - center.x, y - center.y])
28
+ }
@@ -1,4 +1,5 @@
1
1
  // three-slicer-viewer/settings — the catalog-free settings surface. See settings_core.js.
2
2
  export * from './settings_core.js'
3
+ export { bedOrigin, bedCenter, bedShapeLocal } from './bed_frame.js'
3
4
  // The derivation's own fallbacks, for callers that need the same numbers (frozen; see kernel_defaults.js).
4
5
  export { FFF_FALLBACKS, SLA_FALLBACKS, AUTO_LINE_WIDTH, BED_FALLBACK, MACHINE_LIMITS } from './kernel_defaults.js'
@@ -1,19 +1,28 @@
1
1
  // AUTO-GENERATED by types/gen_kernel_params.mjs — DO NOT EDIT.
2
2
  // The schema keys deriveKernelParams consumes (the distinct "From setting" column of engine/PARAMS.md).
3
3
  export const KERNEL_SETTING_KEYS = [
4
+ "accel_to_decel_enable",
5
+ "accel_to_decel_factor",
4
6
  "alternate_extra_wall",
7
+ "before_layer_change_gcode",
5
8
  "bottom_shell_layers",
6
9
  "bottom_solid_infill_flow_ratio",
7
10
  "bottom_surface_filament_id",
11
+ "bridge_acceleration",
8
12
  "bridge_flow",
9
13
  "bridge_no_support",
10
14
  "bridge_speed",
11
15
  "brim_flow_ratio",
12
16
  "brim_object_gap",
13
17
  "brim_width",
18
+ "change_filament_gcode",
14
19
  "close_fan_the_first_x_layers",
20
+ "curr_bed_type",
15
21
  "default_acceleration",
22
+ "default_jerk",
16
23
  "detect_thin_wall",
24
+ "disable_m73",
25
+ "emit_machine_limits_to_gcode",
17
26
  "enable_arc_fitting",
18
27
  "enable_pressure_advance",
19
28
  "enable_prime_tower",
@@ -30,8 +39,10 @@ export const KERNEL_SETTING_KEYS = [
30
39
  "filament_retraction_minimum_travel",
31
40
  "filament_retraction_speed",
32
41
  "filament_settings_id",
42
+ "filament_start_gcode",
33
43
  "filament_type",
34
44
  "filament_z_hop",
45
+ "file_start_gcode",
35
46
  "filter_out_gap_fill",
36
47
  "first_layer_flow_ratio",
37
48
  "flush_into_infill",
@@ -40,32 +51,51 @@ export const KERNEL_SETTING_KEYS = [
40
51
  "full_fan_speed_layer",
41
52
  "gap_fill_flow_ratio",
42
53
  "gap_infill_speed",
54
+ "gcode_flavor",
43
55
  "hot_plate_temp",
56
+ "hot_plate_temp_initial_layer",
44
57
  "independent_support_layer_height",
45
58
  "infill_direction",
59
+ "infill_jerk",
46
60
  "infill_wall_overlap",
61
+ "initial_layer_acceleration",
62
+ "initial_layer_infill_speed",
63
+ "initial_layer_jerk",
47
64
  "initial_layer_line_width",
48
65
  "initial_layer_print_height",
49
66
  "initial_layer_speed",
67
+ "initial_layer_travel_acceleration",
68
+ "initial_layer_travel_jerk",
69
+ "initial_layer_travel_speed",
70
+ "inner_wall_acceleration",
50
71
  "inner_wall_filament_id",
51
72
  "inner_wall_flow_ratio",
73
+ "inner_wall_jerk",
52
74
  "inner_wall_line_width",
75
+ "inner_wall_speed",
53
76
  "internal_solid_filament_id",
77
+ "internal_solid_infill_acceleration",
54
78
  "internal_solid_infill_flow_ratio",
55
79
  "internal_solid_infill_line_width",
80
+ "internal_solid_infill_speed",
56
81
  "ironing_flow",
57
82
  "ironing_spacing",
58
83
  "ironing_speed",
59
84
  "ironing_type",
85
+ "layer_change_gcode",
60
86
  "layer_height",
61
87
  "line_width",
62
88
  "machine_end_gcode",
63
89
  "machine_max_acceleration_e",
90
+ "machine_max_acceleration_extruding",
64
91
  "machine_max_acceleration_retracting",
92
+ "machine_max_acceleration_travel",
65
93
  "machine_max_acceleration_x",
94
+ "machine_max_acceleration_y",
66
95
  "machine_max_acceleration_z",
67
96
  "machine_max_jerk_e",
68
97
  "machine_max_jerk_x",
98
+ "machine_max_jerk_y",
69
99
  "machine_max_jerk_z",
70
100
  "machine_max_speed_e",
71
101
  "machine_max_speed_x",
@@ -74,9 +104,12 @@ export const KERNEL_SETTING_KEYS = [
74
104
  "max_volumetric_extrusion_rate_slope",
75
105
  "nozzle_diameter",
76
106
  "nozzle_temperature",
107
+ "nozzle_temperature_initial_layer",
77
108
  "only_one_wall_first_layer",
109
+ "outer_wall_acceleration",
78
110
  "outer_wall_filament_id",
79
111
  "outer_wall_flow_ratio",
112
+ "outer_wall_jerk",
80
113
  "outer_wall_line_width",
81
114
  "outer_wall_speed",
82
115
  "precise_outer_wall",
@@ -90,6 +123,7 @@ export const KERNEL_SETTING_KEYS = [
90
123
  "raft_expansion",
91
124
  "raft_layers",
92
125
  "reduce_crossing_wall",
126
+ "reduce_infill_retraction",
93
127
  "resolution",
94
128
  "retraction_length",
95
129
  "retraction_minimum_travel",
@@ -98,15 +132,22 @@ export const KERNEL_SETTING_KEYS = [
98
132
  "seam_position",
99
133
  "seam_slope_type",
100
134
  "set_other_flow_ratios",
135
+ "single_extruder_multi_material",
101
136
  "skirt_distance",
102
137
  "skirt_height",
103
138
  "skirt_loops",
139
+ "skirt_speed",
104
140
  "slow_down_layer_time",
141
+ "slow_down_layers",
142
+ "small_perimeter_speed",
143
+ "small_perimeter_threshold",
144
+ "sparse_infill_acceleration",
105
145
  "sparse_infill_density",
106
146
  "sparse_infill_filament_id",
107
147
  "sparse_infill_flow_ratio",
108
148
  "sparse_infill_line_width",
109
149
  "sparse_infill_pattern",
150
+ "sparse_infill_speed",
110
151
  "spiral_mode",
111
152
  "support_angle",
112
153
  "support_base_pattern",
@@ -120,24 +161,31 @@ export const KERNEL_SETTING_KEYS = [
120
161
  "support_interface_flow_ratio",
121
162
  "support_interface_pattern",
122
163
  "support_interface_spacing",
164
+ "support_interface_speed",
123
165
  "support_interface_top_layers",
124
166
  "support_line_width",
125
167
  "support_object_first_layer_gap",
126
168
  "support_object_xy_distance",
127
169
  "support_on_build_plate_only",
128
170
  "support_remove_small_overhang",
171
+ "support_speed",
129
172
  "support_style",
130
173
  "support_threshold_angle",
131
174
  "support_threshold_overlap",
132
175
  "support_top_z_distance",
133
176
  "support_type",
134
177
  "thick_bridges",
178
+ "time_lapse_gcode",
135
179
  "top_bottom_infill_wall_overlap",
136
180
  "top_shell_layers",
137
181
  "top_solid_infill_flow_ratio",
182
+ "top_surface_acceleration",
138
183
  "top_surface_filament_id",
184
+ "top_surface_jerk",
139
185
  "top_surface_line_width",
186
+ "top_surface_speed",
140
187
  "travel_acceleration",
188
+ "travel_jerk",
141
189
  "travel_speed",
142
190
  "tree_support_angle_slow",
143
191
  "tree_support_branch_angle",
@@ -150,6 +198,8 @@ export const KERNEL_SETTING_KEYS = [
150
198
  "tree_support_tip_diameter",
151
199
  "tree_support_top_rate",
152
200
  "tree_support_wall_count",
201
+ "use_firmware_retraction",
202
+ "use_relative_e_distances",
153
203
  "wall_direction",
154
204
  "wall_generator",
155
205
  "wall_loops",
@@ -157,4 +207,5 @@ export const KERNEL_SETTING_KEYS = [
157
207
  "wipe_tower_x",
158
208
  "wipe_tower_y",
159
209
  "z_hop",
210
+ "z_offset",
160
211
  ]
@@ -66,8 +66,12 @@ const PASSTHROUGH_NUM = [
66
66
  'scarf_joint_flow_ratio', 'outer_wall_flow_ratio', 'inner_wall_flow_ratio', 'sparse_infill_flow_ratio',
67
67
  'internal_solid_infill_flow_ratio', 'gap_fill_flow_ratio', 'support_flow_ratio', 'support_interface_flow_ratio',
68
68
  'first_layer_flow_ratio',
69
+ // Added to every Z the G-code writes (upstream's GCodeWriter).
70
+ 'z_offset',
69
71
  ]
70
72
  const PASSTHROUGH_BOOL = ['independent_support_layer_height',
73
+ // The machine's E mode and firmware retraction (upstream GCodeWriter), each a printer profile option.
74
+ 'use_relative_e_distances', 'use_firmware_retraction',
71
75
  'detect_thin_wall', 'precise_outer_wall', 'only_one_wall_first_layer', 'alternate_extra_wall',
72
76
  // thick_bridges picks upstream's round bridge thread; set_other_flow_ratios turns the per-role ratios on.
73
77
  'thick_bridges', 'set_other_flow_ratios']
@@ -82,6 +86,28 @@ const PASSTHROUGH_STR = ['wall_sequence', 'wall_direction']
82
86
  // kernel reads its first entry, the wall filament's on the single-material path. filament_max_volumetric_speed caps
83
87
  // every extrusion's speed as upstream does (GCode.cpp:7492).
84
88
  const PASSTHROUGH_NUM_VECTOR = ['filament_map', 'filament_density', 'filament_cost', 'gap_infill_speed', 'filament_max_volumetric_speed']
89
+ // Per-role speed, acceleration and jerk, upstream's GCode::_extrude and travel_to (GCode.cpp:7292-7342, :7393-7473,
90
+ // :8289-8333). Sent under the upstream names, only when the map holds the key (or the map carries a custom G-code,
91
+ // see followsTemplate). A percent option is resolved against the option upstream takes it as a ratio over
92
+ // (PrintConfig ratio_over), the second entry. The kernel keeps print_speed for every role it gets no speed for, and
93
+ // writes no acceleration or jerk at all without default_acceleration / default_jerk.
94
+ const MOTION_KEYS = [
95
+ ['inner_wall_speed'], ['sparse_infill_speed'], ['internal_solid_infill_speed'], ['top_surface_speed'],
96
+ ['support_speed'], ['support_interface_speed'], ['initial_layer_infill_speed'], ['skirt_speed'], ['slow_down_layers'],
97
+ ['small_perimeter_speed', 'outer_wall_speed'], ['small_perimeter_threshold'],
98
+ ['initial_layer_travel_speed', 'travel_speed'],
99
+ ['default_acceleration'], ['outer_wall_acceleration'], ['inner_wall_acceleration'], ['top_surface_acceleration'],
100
+ ['sparse_infill_acceleration', 'default_acceleration'], ['internal_solid_infill_acceleration', 'default_acceleration'],
101
+ ['bridge_acceleration', 'outer_wall_acceleration'], ['initial_layer_acceleration'], ['travel_acceleration'],
102
+ ['initial_layer_travel_acceleration', 'travel_acceleration'],
103
+ ['default_jerk'], ['outer_wall_jerk'], ['inner_wall_jerk'], ['top_surface_jerk'], ['infill_jerk'], ['initial_layer_jerk'],
104
+ ['travel_jerk'], ['initial_layer_travel_jerk', 'travel_jerk'],
105
+ // The writer's clamps (GCodeWriter::apply_print_config) and Klipper's ACCEL_TO_DECEL.
106
+ ['machine_max_acceleration_extruding'], ['machine_max_acceleration_travel'], ['machine_max_acceleration_y'], ['machine_max_jerk_y'],
107
+ ['accel_to_decel_factor'],
108
+ ]
109
+ const MOTION_BOOLS = ['accel_to_decel_enable']
110
+
85
111
  // Material identity. The kernel needs the type for the two decisions upstream makes by material name (PETG's
86
112
  // extra unretract, TPU on the first layer) and the settings id for the footer.
87
113
  const PASSTHROUGH_STR_VECTOR = ['filament_type', 'filament_settings_id']
@@ -215,21 +241,76 @@ export function serializeProjectSettings(settings) {
215
241
  // project_settings.config stores (serializeProjectSettings), as one JSON string: the kernel's flat key search
216
242
  // never looks inside a string value, so no key in it can be taken for a kernel parameter the map omits.
217
243
  // The kernel names the same templates in custom_gcode.cpp; test_custom_gcode.mjs checks each key here is expanded there.
218
- export const CUSTOM_GCODE_KEYS = ['machine_start_gcode', 'machine_end_gcode', 'filament_end_gcode']
244
+ export const CUSTOM_GCODE_KEYS = ['machine_start_gcode', 'machine_end_gcode', 'filament_end_gcode', 'filament_start_gcode',
245
+ 'before_layer_change_gcode', 'layer_change_gcode', 'time_lapse_gcode', 'file_start_gcode', 'change_filament_gcode']
219
246
 
220
- function placeholderConfig(settings, plate) {
247
+ // Whether the map carries a printer's custom G-code — the switch between the kernel's own raw path and upstream's
248
+ // full semantics, in which every option exists with its schema default (custom_gcode_bridge reads them that way).
249
+ function hasCustomGcode(settings) {
221
250
  const hasText = key => [settings?.[key]].flat().some(value => String(value ?? '').trim() !== '')
222
- if (!CUSTOM_GCODE_KEYS.some(hasText)) return {}
251
+ return CUSTOM_GCODE_KEYS.some(hasText)
252
+ }
253
+
254
+ // What a printer profile asks of a tool change that the template path does not do yet. The slice is not refused —
255
+ // that would stop every multi-material print — but each key whose value would change the output is named
256
+ // (PROFILE_PARTIAL_SUPPORT), so the setting is not ignored silently. A key the map omits reads as upstream's schema
257
+ // default, as the template path does (custom_gcode_bridge).
258
+ // ponytail: tool changes only, the gaps listed in custom_gcode.h. Overhang speeds (no ExtrusionQualityEstimator) are
259
+ // on in every profile, so a notice for them would show on every slice; add them when the notice has a better home.
260
+ export const PARTIAL_SUPPORT_CODE = 'PROFILE_PARTIAL_SUPPORT'
261
+ const TOOL_CHANGE_GAPS = [
262
+ { key: 'retract_length_toolchange', isSet: values => values.some(value => parseFloat(value) > 0),
263
+ effect: 'the retraction before a tool change uses retraction_length' },
264
+ { key: 'ooze_prevention', isSet: values => values.some(value => value === true || value === 1 || value === '1' || value === 'true'),
265
+ effect: 'idle tools are not parked at a standby temperature' },
266
+ ]
267
+ export function partialSupport(settings, { toolCount = 1 } = {}) {
268
+ if (!hasCustomGcode(settings) || toolCount < 2) return []
269
+ const gaps = []
270
+ for (const gap of TOOL_CHANGE_GAPS) {
271
+ let value = settings?.[gap.key]
272
+ if (value == null || value === '') value = schemaDefault(gap.key)
273
+ if (gap.isSet([value].flat())) gaps.push({ code: PARTIAL_SUPPORT_CODE, key: gap.key, effect: gap.effect })
274
+ }
275
+ return gaps
276
+ }
277
+
278
+ function placeholderConfig(settings, plate, objectNames) {
279
+ if (!hasCustomGcode(settings)) return {}
223
280
  // upstream's Print::get_plate_number_formatted: the 1-based plate, zero-padded to two digits
224
281
  const plateNumber = String(plate + 1).padStart(2, '0')
225
- return { placeholder_config: JSON.stringify({ ...serializeProjectSettings(settings), $plate_number: plateNumber }) }
282
+ // $object_names: the printable objects in merge order, from which the kernel derives upstream's object
283
+ // placeholders (input_filename_base, first_object_name, num_objects — PrintBase::update_object_placeholders).
284
+ const objects = {}
285
+ if (objectNames?.length) objects.$object_names = objectNames.map(name => String(name ?? ''))
286
+ // The plate type the kernel's own temperatures follow (deriveKernelParams), so the template's bed temperature and
287
+ // the second layer's agree about what the bed was heated to.
288
+ const bedType = { curr_bed_type: settings?.curr_bed_type ?? DEFAULT_BED_TYPE }
289
+ return { placeholder_config: JSON.stringify({ ...serializeProjectSettings({ ...settings, ...bedType }), $plate_number: plateNumber, ...objects }) }
290
+ }
291
+
292
+ // The bed temperature option of each plate type, upstream's get_bed_temp_key (PrintConfig.hpp:573). The first-layer
293
+ // option is the same key with `_initial_layer` (get_bed_temp_1st_layer_key).
294
+ // The plate type a printer gets when it names none: upstream's Plater writes Preset::get_default_bed_type to
295
+ // curr_bed_type on every printer change, and that falls back to btPEI (Preset.cpp:993). The schema's own default
296
+ // ("Cool Plate") is never what a sliced printer runs with. hot_plate_temp is also the option this kernel always read.
297
+ export const DEFAULT_BED_TYPE = 'High Temp Plate'
298
+ const BED_TEMP_KEYS = {
299
+ 'Supertack Plate': 'supertack_plate_temp', 'Cool Plate': 'cool_plate_temp', 'Textured Cool Plate': 'textured_cool_plate_temp',
300
+ 'Engineering Plate': 'eng_plate_temp', 'High Temp Plate': 'hot_plate_temp', 'Textured PEI Plate': 'textured_plate_temp',
226
301
  }
227
302
 
228
303
  // Right-panel settings values -> kernel parameters (derived from schema keys)
229
304
  // opts.plate: which plate's entry to take from per-plate array options (wipe_tower_x/y — upstream coFloats,
230
305
  // one entry per plate). Defaults to 0, so every existing caller reads exactly what it always did.
306
+ // opts.objectNames: the merged objects' names, for the custom G-code's object placeholders (see placeholderConfig).
231
307
  export function deriveKernelParams(settings, opts) {
232
308
  const plate = opts?.plate ?? 0
309
+ // With a printer's custom G-code the kernel expands it against every option at its default (custom_gcode_bridge),
310
+ // so the machine options it reads itself take their defaults too — otherwise the two halves of one G-code would
311
+ // disagree. Without one, the omission rule: only keys the map holds are sent.
312
+ const followsTemplate = hasCustomGcode(settings)
313
+ const presentOrTemplate = (key) => followsTemplate || (settings != null && key in settings && settings[key] != null && settings[key] !== '')
233
314
  const S = k => settingScalar(settings, k)
234
315
  const str = k => String(S(k) ?? FFF_FALLBACKS[k])
235
316
  const num = (k, d = FFF_FALLBACKS[k]) => { const v = Number(S(k)); return Number.isFinite(v) ? v : d }
@@ -269,12 +350,17 @@ export function deriveKernelParams(settings, opts) {
269
350
  // style never reached the kernel and every tree style sliced organic.
270
351
  const tree_style = { tree_slim: 'slim', tree_strong: 'strong', tree_hybrid: 'hybrid' }[styleRaw]
271
352
 
272
- // Bed: bounding box of the first rectangle in printable_area
353
+ // Bed: bounding box of the first rectangle in printable_area. Its lower-left corner is sent only when it is not
354
+ // (0,0) — a delta bed centred on the origin, or an offset cartesian one — so every bed that starts at the corner
355
+ // keeps exactly the parameters it had (see bedCenter below for what the kernel does with it).
273
356
  const pa = settingRaw(settings, 'printable_area')
274
357
  let bed_width = BED_FALLBACK.width, bed_depth = BED_FALLBACK.depth
358
+ const bedOriginParams = {} // read back through bedOrigin/bedCenter (bed_frame.js)
275
359
  if (Array.isArray(pa) && pa.length) {
276
360
  const xs = pa.map(p => p[0]), ys = pa.map(p => p[1])
277
361
  bed_width = Math.max(...xs) - Math.min(...xs); bed_depth = Math.max(...ys) - Math.min(...ys)
362
+ if (Math.min(...xs) !== 0) bedOriginParams.bed_origin_x = Math.min(...xs)
363
+ if (Math.min(...ys) !== 0) bedOriginParams.bed_origin_y = Math.min(...ys)
278
364
  }
279
365
 
280
366
  // Stage 21: per-feature widths — passed to the kernel verbatim (including strings like "120%"). Unedited keys are omitted -> the kernel derives auto(=line_width).
@@ -374,13 +460,60 @@ export function deriveKernelParams(settings, opts) {
374
460
  }
375
461
  }
376
462
 
463
+ const motion = {}
464
+ for (const [key, ratioOver] of MOTION_KEYS) {
465
+ if (!presentOrTemplate(key)) continue
466
+ const raw = settingScalar(settings, key)
467
+ let value = Number(raw)
468
+ // A percent of another option (ratio_over) resolves against it; a plain coPercent (accel_to_decel_factor "50%")
469
+ // is the number itself.
470
+ if (typeof raw === 'string' && raw.trim().endsWith('%')) {
471
+ value = parseFloat(raw)
472
+ if (ratioOver) value = value / 100 * num(ratioOver, 0)
473
+ }
474
+ if (Number.isFinite(value)) motion[key] = value
475
+ }
476
+ for (const key of MOTION_BOOLS) if (presentOrTemplate(key)) motion[key] = bool(key, false)
477
+
478
+ // emit_machine_limits_to_gcode: the machine's limits written into the G-code (upstream print_machine_envelope),
479
+ // as the 16 numbers machine_writer.h reads in this order. Sent only when the profile turns it on.
480
+ const envelope = {}
481
+ if (presentOrTemplate('emit_machine_limits_to_gcode') && bool('emit_machine_limits_to_gcode', false)) {
482
+ envelope.machine_envelope = ['machine_max_acceleration_x', 'machine_max_acceleration_y', 'machine_max_acceleration_z',
483
+ 'machine_max_acceleration_e', 'machine_max_speed_x', 'machine_max_speed_y', 'machine_max_speed_z', 'machine_max_speed_e',
484
+ 'machine_max_acceleration_extruding', 'machine_max_acceleration_retracting', 'machine_max_acceleration_travel',
485
+ 'machine_max_jerk_x', 'machine_max_jerk_y', 'machine_max_jerk_z', 'machine_max_jerk_e', 'machine_max_junction_deviation',
486
+ ].map(key => num(key, 0))
487
+ }
488
+
377
489
  // Sent only for the three non-organic styles (see tree_style above), so an empty map still derives the same keys.
378
490
  const treeStyle = {}
379
491
  if (tree_style) treeStyle.tree_style = tree_style
380
492
 
493
+ // Temperatures. The bed's option follows the plate type upstream does (get_bed_temp_key). The first layer's own
494
+ // temperatures (nozzle_temperature_initial_layer, <plate>_temp_initial_layer) start the print and the kernel
495
+ // switches at the second layer, as upstream's process_layer does (GCode.cpp:5631). With a printer's custom
496
+ // G-code the start block's temperatures come from custom_gcode_bridge, which reads every option with its default,
497
+ // so the kernel takes the first-layer ones the same way or the two would disagree about what the print started
498
+ // at. Without one, only keys the map holds are sent. A map without curr_bed_type is on DEFAULT_BED_TYPE.
499
+ const bedTempKey = BED_TEMP_KEYS[settings?.curr_bed_type ?? DEFAULT_BED_TYPE] ?? 'hot_plate_temp'
500
+ const printerCommands = { bed_temp: num(bedTempKey, FFF_FALLBACKS.hot_plate_temp) }
501
+ if (presentOrTemplate('nozzle_temperature_initial_layer')) printerCommands.first_layer_nozzle_temp = num('nozzle_temperature_initial_layer')
502
+ if (presentOrTemplate(`${bedTempKey}_initial_layer`)) printerCommands.first_layer_bed_temp = num(`${bedTempKey}_initial_layer`, FFF_FALLBACKS.hot_plate_temp)
503
+ // Whether one nozzle carries every filament (upstream single_extruder_multi_material): the second-layer switch
504
+ // then names no tool and sets only the loaded one.
505
+ if (presentOrTemplate('single_extruder_multi_material')) printerCommands.single_extruder_multi_material = bool('single_extruder_multi_material', true)
506
+ // No retraction for a travel inside the infill (upstream GCode::needs_retraction); upstream's default is on.
507
+ if (presentOrTemplate('reduce_infill_retraction')) printerCommands.reduce_infill_retraction = bool('reduce_infill_retraction', true)
508
+ // The firmware flavor formats the machine commands (machine_writer.h); upstream's default is "marlin".
509
+ if (presentOrTemplate('gcode_flavor')) printerCommands.gcode_flavor = str('gcode_flavor')
510
+ // disable_m73 false: the G-code carries upstream's M73 progress placeholders (finalizeGcode fills them).
511
+ if (presentOrTemplate('disable_m73')) printerCommands.disable_m73 = bool('disable_m73', false)
512
+
381
513
  const perExtruder = {}
382
514
  for (const [param, key, scalar] of [
383
515
  ['extruder_nozzle_temp', 'nozzle_temperature', num('nozzle_temperature')],
516
+ ['extruder_first_layer_temp', 'nozzle_temperature_initial_layer', num('nozzle_temperature_initial_layer')],
384
517
  ['extruder_filament_diameter', 'filament_diameter', num('filament_diameter')],
385
518
  ['extruder_flow_ratio', 'filament_flow_ratio', num('filament_flow_ratio')],
386
519
  ['extruder_retract_length', 'filament_retraction_length', override('filament_retraction_length', 'retraction_length')],
@@ -401,6 +534,8 @@ export function deriveKernelParams(settings, opts) {
401
534
  ...machine,
402
535
  ...passthrough, // present only for keys the settings map actually holds (see PASSTHROUGH_* above)
403
536
  ...treeStyle,
537
+ ...envelope,
538
+ ...motion,
404
539
  layer_height: num('layer_height'),
405
540
  first_layer_height: num('initial_layer_print_height'),
406
541
  line_width,
@@ -434,14 +569,15 @@ export function deriveKernelParams(settings, opts) {
434
569
  filament_diameter: num('filament_diameter'),
435
570
  flow_ratio: num('filament_flow_ratio'),
436
571
  nozzle_temp: num('nozzle_temperature'),
437
- bed_temp: num('hot_plate_temp'), // no bed_temperature key -> hot_plate_temp
572
+ ...printerCommands, // temperatures (bed by plate type, the first layer's own) and the flavor
438
573
  bed_width, bed_depth,
574
+ ...bedOriginParams,
439
575
  bed_height: num('printable_height'), // 0 = profile states no ceiling -> kernel skips the check
440
576
  // Explicitly-set only: the schema default here is upstream's generic "G28 / G1 Z5" preamble, and falling back
441
577
  // to it would rewrite the emitted G-code for every caller. The kernel keeps its own preamble when these are empty.
442
578
  machine_start_gcode: String(settings?.machine_start_gcode ?? ''),
443
579
  machine_end_gcode: String(settings?.machine_end_gcode ?? ''),
444
- ...placeholderConfig(settings, plate),
580
+ ...placeholderConfig(settings, plate, opts?.objectNames),
445
581
  enable_support: bool('enable_support'),
446
582
  support_threshold_angle: num('support_threshold_angle'),
447
583
  support_top_z_distance: num('support_top_z_distance'),
package/types/gcode.d.ts CHANGED
@@ -9,6 +9,13 @@ export interface GcodeLayer {
9
9
  widths: Float32Array
10
10
  }
11
11
 
12
+ /**
13
+ * Fills the placeholders a slice leaves for its finished estimate, as upstream's GCodeProcessor post-process does:
14
+ * the M73 progress lines (`;_GP_FIRST_LINE_M73_PLACEHOLDER` / `;_GP_LAST_LINE_M73_PLACEHOLDER`, one M73 per layer
15
+ * start) and file_start_gcode's `@PRINT_TIME_SEC@` / `@USED_FILAMENT_LENGTH@`. A G-code without them is returned as is.
16
+ */
17
+ export function finalizeGcode(gcode: string, stats: { time_estimate?: number; filament_mm?: number; layer_times?: number[] } | null | undefined): string
18
+
12
19
  export interface ParseGcodeResult {
13
20
  layers: GcodeLayer[]
14
21
  stats: {
@@ -53,3 +60,9 @@ export interface ParseGcodeOptions {
53
60
  * Recovery is lossy where G-code carries no data: unknown roles become wall(1), widths are derived from E.
54
61
  */
55
62
  export function parseGcode(text: string, opts?: ParseGcodeOptions): ParseGcodeResult
63
+
64
+ /**
65
+ * A slice as it is shown: its exported G-code parsed back (start and end G-code extrusions included),
66
+ * moved from printer coordinates into the slice's plate-local frame by taking the bed centre off.
67
+ */
68
+ export function previewFromGcode(gcode: string, opts?: { center?: { x: number; y: number }; filamentDiameter?: number; layerHeight?: number }): ParseGcodeResult
@@ -16,7 +16,20 @@ export function settingScalar(settings: SlicerSettings | null | undefined, key:
16
16
  * ponytail: 53 returned keys, so Record instead of listing each — kernel params are passed straight to slice(),
17
17
  * not an API consumers read field by field. Expand it when field access is actually needed.
18
18
  */
19
- export function deriveKernelParams(settings: SlicerSettings | null | undefined, opts?: { plate?: number }): Record<string, unknown>
19
+ export function deriveKernelParams(settings: SlicerSettings | null | undefined, opts?: { plate?: number; objectNames?: string[] }): Record<string, unknown>
20
+ /**
21
+ * The printable area's lower-left corner and its centre in printer coordinates, from deriveKernelParams' output.
22
+ * The kernel slices in a plate frame centred on the bed and adds the centre on output, so a stored printer
23
+ * coordinate (wipe_tower_x/y, a 3mf position, G-code) converts to that frame by subtracting `bedCenter`.
24
+ */
25
+ export function bedOrigin(params: Record<string, unknown> | null | undefined): { x: number; y: number }
26
+ /** Code of a printer profile setting the template path reads but does not do yet. */
27
+ export const PARTIAL_SUPPORT_CODE: 'PROFILE_PARTIAL_SUPPORT'
28
+ /** The tool change settings a profile sets that the template path does not apply (empty off the template path or
29
+ * with one tool), each with what happens instead. */
30
+ export function partialSupport(settings: SlicerSettings | null | undefined, opts?: { toolCount?: number }):
31
+ { code: 'PROFILE_PARTIAL_SUPPORT'; key: string; effect: string }[]
32
+ export function bedCenter(params: Record<string, unknown> | null | undefined): { x: number; y: number }
20
33
 
21
34
  /**
22
35
  * Clear-then-merge preset application: deletes `keys` (the catalog's own key set) from `settings`, then
@@ -39,6 +39,7 @@ export interface SegmentMeta {
39
39
 
40
40
  export interface SegmentData {
41
41
  position: Float32Array
42
+ /** vec4 per vertex: [height, width, heading of the extrusion joined at this endpoint (its own when none), packed colour] */
42
43
  hwa: Float32Array
43
44
  segIndex: Uint32Array
44
45
  nV: number
@@ -110,6 +111,11 @@ export interface ToolpathHandle {
110
111
  setVisibleLayers(n: number): void
111
112
  setTravelVisible(visible: boolean): void
112
113
  setColors(color: Float32Array): void
114
+ /** Draws the top shown layer up to its move `at`, extrusions and travels in print order; null draws the whole
115
+ * range again. Returns where the nozzle is (moveCursor) and the layer walked, or null. */
116
+ setMoveRange(at: number | null): { point: number[] | null; segCount: number; travCount: number; onTravel: boolean; layer: number } | null
117
+ /** The SegmentData the handle was built from. */
118
+ data: SegmentData
113
119
  dispose(): void
114
120
  nSeg: number
115
121
  layerCount: number