three-slicer-viewer 0.3.3 → 0.3.5
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/TOOLPATH_SPEC.md +8 -1
- package/data/config-schema-lean.json +1 -1
- package/dist/gcode_parse.js +149 -110
- package/dist/index.js +4547 -4304
- package/dist/sl1_encode.worker.js +1 -1
- package/dist/sl1_write-B6nWmCUu.js +313 -0
- package/dist/sla_slice.worker.js +1 -1
- package/dist/toolpath.js +274 -220
- package/package.json +1 -1
- package/src/settings/bed_frame.js +28 -0
- package/src/settings/index.js +1 -0
- package/src/settings/kernel_setting_keys.js +51 -0
- package/src/settings/settings_core.js +253 -60
- package/types/gcode.d.ts +13 -0
- package/types/settings.d.ts +14 -1
- package/types/toolpath.d.ts +6 -0
- package/dist/sl1_write-BovHbg3k.js +0 -303
|
@@ -14,7 +14,10 @@ import { leanSchema as schema } from './data.js'
|
|
|
14
14
|
import { FFF_FALLBACKS, AUTO_LINE_WIDTH, SLA_FALLBACKS, MACHINE_LIMITS, BED_FALLBACK } from './kernel_defaults.js'
|
|
15
15
|
|
|
16
16
|
export function schemaDefault(key) { return schema[key]?.default }
|
|
17
|
-
export function settingRaw(settings, key) {
|
|
17
|
+
export function settingRaw(settings, key) {
|
|
18
|
+
if (settings && key in settings) return settings[key]
|
|
19
|
+
return schemaDefault(key)
|
|
20
|
+
}
|
|
18
21
|
// Normalize to a scalar ([0] for vectors)
|
|
19
22
|
// A per-extruder column reduced to the single value the kernel's scalar reader wants. The FIRST SET entry, not
|
|
20
23
|
// literally index 0: the filament card writes one column per extruder and leaves a null wherever that extruder has
|
|
@@ -66,8 +69,12 @@ const PASSTHROUGH_NUM = [
|
|
|
66
69
|
'scarf_joint_flow_ratio', 'outer_wall_flow_ratio', 'inner_wall_flow_ratio', 'sparse_infill_flow_ratio',
|
|
67
70
|
'internal_solid_infill_flow_ratio', 'gap_fill_flow_ratio', 'support_flow_ratio', 'support_interface_flow_ratio',
|
|
68
71
|
'first_layer_flow_ratio',
|
|
72
|
+
// Added to every Z the G-code writes (upstream's GCodeWriter).
|
|
73
|
+
'z_offset',
|
|
69
74
|
]
|
|
70
75
|
const PASSTHROUGH_BOOL = ['independent_support_layer_height',
|
|
76
|
+
// The machine's E mode and firmware retraction (upstream GCodeWriter), each a printer profile option.
|
|
77
|
+
'use_relative_e_distances', 'use_firmware_retraction',
|
|
71
78
|
'detect_thin_wall', 'precise_outer_wall', 'only_one_wall_first_layer', 'alternate_extra_wall',
|
|
72
79
|
// thick_bridges picks upstream's round bridge thread; set_other_flow_ratios turns the per-role ratios on.
|
|
73
80
|
'thick_bridges', 'set_other_flow_ratios']
|
|
@@ -82,6 +89,28 @@ const PASSTHROUGH_STR = ['wall_sequence', 'wall_direction']
|
|
|
82
89
|
// kernel reads its first entry, the wall filament's on the single-material path. filament_max_volumetric_speed caps
|
|
83
90
|
// every extrusion's speed as upstream does (GCode.cpp:7492).
|
|
84
91
|
const PASSTHROUGH_NUM_VECTOR = ['filament_map', 'filament_density', 'filament_cost', 'gap_infill_speed', 'filament_max_volumetric_speed']
|
|
92
|
+
// Per-role speed, acceleration and jerk, upstream's GCode::_extrude and travel_to (GCode.cpp:7292-7342, :7393-7473,
|
|
93
|
+
// :8289-8333). Sent under the upstream names, only when the map holds the key (or the map carries a custom G-code,
|
|
94
|
+
// see followsTemplate). A percent option is resolved against the option upstream takes it as a ratio over
|
|
95
|
+
// (PrintConfig ratio_over), the second entry. The kernel keeps print_speed for every role it gets no speed for, and
|
|
96
|
+
// writes no acceleration or jerk at all without default_acceleration / default_jerk.
|
|
97
|
+
const MOTION_KEYS = [
|
|
98
|
+
['inner_wall_speed'], ['sparse_infill_speed'], ['internal_solid_infill_speed'], ['top_surface_speed'],
|
|
99
|
+
['support_speed'], ['support_interface_speed'], ['initial_layer_infill_speed'], ['skirt_speed'], ['slow_down_layers'],
|
|
100
|
+
['small_perimeter_speed', 'outer_wall_speed'], ['small_perimeter_threshold'],
|
|
101
|
+
['initial_layer_travel_speed', 'travel_speed'],
|
|
102
|
+
['default_acceleration'], ['outer_wall_acceleration'], ['inner_wall_acceleration'], ['top_surface_acceleration'],
|
|
103
|
+
['sparse_infill_acceleration', 'default_acceleration'], ['internal_solid_infill_acceleration', 'default_acceleration'],
|
|
104
|
+
['bridge_acceleration', 'outer_wall_acceleration'], ['initial_layer_acceleration'], ['travel_acceleration'],
|
|
105
|
+
['initial_layer_travel_acceleration', 'travel_acceleration'],
|
|
106
|
+
['default_jerk'], ['outer_wall_jerk'], ['inner_wall_jerk'], ['top_surface_jerk'], ['infill_jerk'], ['initial_layer_jerk'],
|
|
107
|
+
['travel_jerk'], ['initial_layer_travel_jerk', 'travel_jerk'],
|
|
108
|
+
// The writer's clamps (GCodeWriter::apply_print_config) and Klipper's ACCEL_TO_DECEL.
|
|
109
|
+
['machine_max_acceleration_extruding'], ['machine_max_acceleration_travel'], ['machine_max_acceleration_y'], ['machine_max_jerk_y'],
|
|
110
|
+
['accel_to_decel_factor'],
|
|
111
|
+
]
|
|
112
|
+
const MOTION_BOOLS = ['accel_to_decel_enable']
|
|
113
|
+
|
|
85
114
|
// Material identity. The kernel needs the type for the two decisions upstream makes by material name (PETG's
|
|
86
115
|
// extra unretract, TPU on the first layer) and the settings id for the footer.
|
|
87
116
|
const PASSTHROUGH_STR_VECTOR = ['filament_type', 'filament_settings_id']
|
|
@@ -117,7 +146,8 @@ function coerceScalar(type, value) {
|
|
|
117
146
|
return asNumber(value.trim().replace(/%$/, ''))
|
|
118
147
|
case 'coFloatOrPercent': case 'coFloatsOrPercents':
|
|
119
148
|
// "50%" must stay a string — the percent IS the value, and Number('50%') is NaN anyway.
|
|
120
|
-
|
|
149
|
+
if (value.trim().endsWith('%')) return value
|
|
150
|
+
return asNumber(value)
|
|
121
151
|
case 'coPoint': case 'coPoints':
|
|
122
152
|
return parsePoint(value) ?? value
|
|
123
153
|
case 'coPointsGroups':
|
|
@@ -135,25 +165,34 @@ function coerceScalar(type, value) {
|
|
|
135
165
|
// so there the comma cannot also separate a point's own coordinates — hence two parsers rather than one regex.
|
|
136
166
|
function parsePoint(text) {
|
|
137
167
|
const split = String(text).split('x')
|
|
138
|
-
|
|
168
|
+
let pair = split
|
|
169
|
+
if (split.length !== 2) pair = String(text).split(',')
|
|
139
170
|
if (pair.length !== 2) return null
|
|
140
171
|
const x = Number(pair[0]), y = Number(pair[1])
|
|
141
|
-
|
|
172
|
+
if (Number.isFinite(x) && Number.isFinite(y)) return [x, y]
|
|
173
|
+
return null
|
|
142
174
|
}
|
|
143
175
|
function parsePointGroup(text) {
|
|
144
176
|
const points = String(text).split(',').map(entry => {
|
|
145
177
|
const [a, b] = entry.split('x')
|
|
146
178
|
const x = Number(a), y = Number(b)
|
|
147
|
-
|
|
179
|
+
if (Number.isFinite(x) && Number.isFinite(y)) return [x, y]
|
|
180
|
+
return null
|
|
148
181
|
})
|
|
149
|
-
|
|
182
|
+
if (points.length && points.every(Boolean)) return points
|
|
183
|
+
return null
|
|
150
184
|
}
|
|
151
185
|
// Number('') is 0 and Number(' ') is 0, neither of which means "the option was set to zero" — an unparsable
|
|
152
186
|
// value is handed back untouched so the schema default keeps applying instead of a fabricated 0.
|
|
153
187
|
function asNumber(value) {
|
|
154
188
|
if (value.trim() === '') return value
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
return finiteOr(Number(value), value)
|
|
190
|
+
}
|
|
191
|
+
// The number when it is finite, otherwise the fallback. The fallback is evaluated by the caller, so it is only
|
|
192
|
+
// used for values that are already at hand.
|
|
193
|
+
function finiteOr(number, fallback) {
|
|
194
|
+
if (Number.isFinite(number)) return number
|
|
195
|
+
return fallback
|
|
157
196
|
}
|
|
158
197
|
|
|
159
198
|
/**
|
|
@@ -167,7 +206,8 @@ export function normalizeProjectSettings(raw) {
|
|
|
167
206
|
for (const [key, value] of Object.entries(raw || {})) {
|
|
168
207
|
const type = schema[key]?.type
|
|
169
208
|
if (!type) { skipped.push(key); continue }
|
|
170
|
-
|
|
209
|
+
if (Array.isArray(value)) settings[key] = value.map(entry => coerceScalar(type, entry))
|
|
210
|
+
else settings[key] = coerceScalar(type, value)
|
|
171
211
|
applied++
|
|
172
212
|
}
|
|
173
213
|
return { settings, applied, skipped }
|
|
@@ -178,17 +218,22 @@ export function normalizeProjectSettings(raw) {
|
|
|
178
218
|
// slicer's reader — and normalizeProjectSettings above — expects to coerce FROM. Writing the JS types raw would
|
|
179
219
|
// hand OrcaSlicer a bool it reads with its own string parser and this package a round-trip that only works here.
|
|
180
220
|
// Keys the schema does not define are dropped for the same reason normalize drops them: they could not be typed.
|
|
181
|
-
const pointToText = (point) =>
|
|
221
|
+
const pointToText = (point) => {
|
|
222
|
+
if (Array.isArray(point)) return `${point[0]}x${point[1]}`
|
|
223
|
+
return String(point)
|
|
224
|
+
}
|
|
182
225
|
|
|
183
226
|
function serializeScalar(type, value) {
|
|
184
227
|
if (value == null) return ''
|
|
185
228
|
switch (type) {
|
|
186
229
|
case 'coBool': case 'coBools':
|
|
187
|
-
|
|
230
|
+
if (value) return '1'
|
|
231
|
+
return '0'
|
|
188
232
|
case 'coPoint': case 'coPoints':
|
|
189
233
|
return pointToText(value)
|
|
190
234
|
case 'coPointsGroups':
|
|
191
|
-
|
|
235
|
+
if (Array.isArray(value)) return value.map(pointToText).join(',')
|
|
236
|
+
return String(value)
|
|
192
237
|
default:
|
|
193
238
|
return String(value) // numbers, percents, strings, enums — String() is upstream's own spelling for all of them
|
|
194
239
|
}
|
|
@@ -201,9 +246,8 @@ export function serializeProjectSettings(settings) {
|
|
|
201
246
|
const type = schema[key]?.type
|
|
202
247
|
if (!type) continue
|
|
203
248
|
// A coPoint's value IS an array ([x, y]) — the generic array path would serialize its two numbers separately.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
: serializeScalar(type, value)
|
|
249
|
+
if (type !== 'coPoint' && Array.isArray(value)) out[key] = value.map(entry => serializeScalar(type, entry))
|
|
250
|
+
else out[key] = serializeScalar(type, value)
|
|
207
251
|
}
|
|
208
252
|
return out
|
|
209
253
|
}
|
|
@@ -215,33 +259,111 @@ export function serializeProjectSettings(settings) {
|
|
|
215
259
|
// project_settings.config stores (serializeProjectSettings), as one JSON string: the kernel's flat key search
|
|
216
260
|
// never looks inside a string value, so no key in it can be taken for a kernel parameter the map omits.
|
|
217
261
|
// 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'
|
|
262
|
+
export const CUSTOM_GCODE_KEYS = ['machine_start_gcode', 'machine_end_gcode', 'filament_end_gcode', 'filament_start_gcode',
|
|
263
|
+
'before_layer_change_gcode', 'layer_change_gcode', 'time_lapse_gcode', 'file_start_gcode', 'change_filament_gcode']
|
|
219
264
|
|
|
220
|
-
|
|
265
|
+
// Whether the map carries a printer's custom G-code — the switch between the kernel's own raw path and upstream's
|
|
266
|
+
// full semantics, in which every option exists with its schema default (custom_gcode_bridge reads them that way).
|
|
267
|
+
function hasCustomGcode(settings) {
|
|
221
268
|
const hasText = key => [settings?.[key]].flat().some(value => String(value ?? '').trim() !== '')
|
|
222
|
-
|
|
269
|
+
return CUSTOM_GCODE_KEYS.some(hasText)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// What a printer profile asks of a tool change that the template path does not do yet. The slice is not refused —
|
|
273
|
+
// that would stop every multi-material print — but each key whose value would change the output is named
|
|
274
|
+
// (PROFILE_PARTIAL_SUPPORT), so the setting is not ignored silently. A key the map omits reads as upstream's schema
|
|
275
|
+
// default, as the template path does (custom_gcode_bridge).
|
|
276
|
+
// ponytail: tool changes only, the gaps listed in custom_gcode.h. Overhang speeds (no ExtrusionQualityEstimator) are
|
|
277
|
+
// on in every profile, so a notice for them would show on every slice; add them when the notice has a better home.
|
|
278
|
+
export const PARTIAL_SUPPORT_CODE = 'PROFILE_PARTIAL_SUPPORT'
|
|
279
|
+
const TOOL_CHANGE_GAPS = [
|
|
280
|
+
{ key: 'retract_length_toolchange', isSet: values => values.some(value => parseFloat(value) > 0),
|
|
281
|
+
effect: 'the retraction before a tool change uses retraction_length' },
|
|
282
|
+
{ key: 'ooze_prevention', isSet: values => values.some(value => value === true || value === 1 || value === '1' || value === 'true'),
|
|
283
|
+
effect: 'idle tools are not parked at a standby temperature' },
|
|
284
|
+
]
|
|
285
|
+
export function partialSupport(settings, { toolCount = 1 } = {}) {
|
|
286
|
+
if (!hasCustomGcode(settings) || toolCount < 2) return []
|
|
287
|
+
const gaps = []
|
|
288
|
+
for (const gap of TOOL_CHANGE_GAPS) {
|
|
289
|
+
let value = settings?.[gap.key]
|
|
290
|
+
if (value == null || value === '') value = schemaDefault(gap.key)
|
|
291
|
+
if (gap.isSet([value].flat())) gaps.push({ code: PARTIAL_SUPPORT_CODE, key: gap.key, effect: gap.effect })
|
|
292
|
+
}
|
|
293
|
+
return gaps
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function placeholderConfig(settings, plate, objectNames) {
|
|
297
|
+
if (!hasCustomGcode(settings)) return {}
|
|
223
298
|
// upstream's Print::get_plate_number_formatted: the 1-based plate, zero-padded to two digits
|
|
224
299
|
const plateNumber = String(plate + 1).padStart(2, '0')
|
|
225
|
-
|
|
300
|
+
// $object_names: the printable objects in merge order, from which the kernel derives upstream's object
|
|
301
|
+
// placeholders (input_filename_base, first_object_name, num_objects — PrintBase::update_object_placeholders).
|
|
302
|
+
const objects = {}
|
|
303
|
+
if (objectNames?.length) objects.$object_names = objectNames.map(name => String(name ?? ''))
|
|
304
|
+
// The plate type the kernel's own temperatures follow (deriveKernelParams), so the template's bed temperature and
|
|
305
|
+
// the second layer's agree about what the bed was heated to.
|
|
306
|
+
const bedType = { curr_bed_type: settings?.curr_bed_type ?? DEFAULT_BED_TYPE }
|
|
307
|
+
return { placeholder_config: JSON.stringify({ ...serializeProjectSettings({ ...settings, ...bedType }), $plate_number: plateNumber, ...objects }) }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// The bed temperature option of each plate type, upstream's get_bed_temp_key (PrintConfig.hpp:573). The first-layer
|
|
311
|
+
// option is the same key with `_initial_layer` (get_bed_temp_1st_layer_key).
|
|
312
|
+
// The plate type a printer gets when it names none: upstream's Plater writes Preset::get_default_bed_type to
|
|
313
|
+
// curr_bed_type on every printer change, and that falls back to btPEI (Preset.cpp:993). The schema's own default
|
|
314
|
+
// ("Cool Plate") is never what a sliced printer runs with. hot_plate_temp is also the option this kernel always read.
|
|
315
|
+
export const DEFAULT_BED_TYPE = 'High Temp Plate'
|
|
316
|
+
const BED_TEMP_KEYS = {
|
|
317
|
+
'Supertack Plate': 'supertack_plate_temp', 'Cool Plate': 'cool_plate_temp', 'Textured Cool Plate': 'textured_cool_plate_temp',
|
|
318
|
+
'Engineering Plate': 'eng_plate_temp', 'High Temp Plate': 'hot_plate_temp', 'Textured PEI Plate': 'textured_plate_temp',
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// A per-extruder column's first entry; a scalar is its own first entry.
|
|
322
|
+
function firstEntryOf(value) {
|
|
323
|
+
if (Array.isArray(value)) return value[0]
|
|
324
|
+
return value
|
|
325
|
+
}
|
|
326
|
+
// A per-extruder column as a list; a scalar becomes a one-entry list.
|
|
327
|
+
function asList(value) {
|
|
328
|
+
if (Array.isArray(value)) return value
|
|
329
|
+
return [value]
|
|
330
|
+
}
|
|
331
|
+
// The value when it is one of the accepted ones, otherwise the fallback.
|
|
332
|
+
function allowedOr(value, accepted, fallback) {
|
|
333
|
+
if (accepted.includes(value)) return value
|
|
334
|
+
return fallback
|
|
226
335
|
}
|
|
227
336
|
|
|
228
337
|
// Right-panel settings values -> kernel parameters (derived from schema keys)
|
|
229
338
|
// opts.plate: which plate's entry to take from per-plate array options (wipe_tower_x/y — upstream coFloats,
|
|
230
339
|
// one entry per plate). Defaults to 0, so every existing caller reads exactly what it always did.
|
|
340
|
+
// opts.objectNames: the merged objects' names, for the custom G-code's object placeholders (see placeholderConfig).
|
|
231
341
|
export function deriveKernelParams(settings, opts) {
|
|
232
342
|
const plate = opts?.plate ?? 0
|
|
343
|
+
// With a printer's custom G-code the kernel expands it against every option at its default (custom_gcode_bridge),
|
|
344
|
+
// so the machine options it reads itself take their defaults too — otherwise the two halves of one G-code would
|
|
345
|
+
// disagree. Without one, the omission rule: only keys the map holds are sent.
|
|
346
|
+
const followsTemplate = hasCustomGcode(settings)
|
|
347
|
+
const presentOrTemplate = (key) => followsTemplate || (settings != null && key in settings && settings[key] != null && settings[key] !== '')
|
|
233
348
|
const S = k => settingScalar(settings, k)
|
|
234
349
|
const str = k => String(S(k) ?? FFF_FALLBACKS[k])
|
|
235
|
-
const num = (k, d = FFF_FALLBACKS[k]) =>
|
|
236
|
-
const bool = (k, d = FFF_FALLBACKS[k]) => {
|
|
350
|
+
const num = (k, d = FFF_FALLBACKS[k]) => finiteOr(Number(S(k)), d)
|
|
351
|
+
const bool = (k, d = FFF_FALLBACKS[k]) => {
|
|
352
|
+
const v = settingRaw(settings, k)
|
|
353
|
+
if (typeof v === 'boolean') return v
|
|
354
|
+
if (Array.isArray(v)) return !!v[0]
|
|
355
|
+
if (v == null) return d
|
|
356
|
+
return !!v
|
|
357
|
+
}
|
|
237
358
|
// Filament retraction overrides: upstream lets a material override the machine's retraction (TPU wants a
|
|
238
359
|
// different pull-back than PLA on the same printer). These schema keys are nullable and carry no default of
|
|
239
360
|
// their own — "unset" means keep the machine value — so this reads the settings map directly rather than
|
|
240
361
|
// through settingScalar, whose schema fallback would not distinguish the two.
|
|
241
362
|
const override = (filamentKey, machineKey, fallback = FFF_FALLBACKS[machineKey]) => {
|
|
242
363
|
const raw = settings?.[filamentKey]
|
|
243
|
-
const value = Number(
|
|
244
|
-
|
|
364
|
+
const value = Number(firstEntryOf(raw))
|
|
365
|
+
if (Number.isFinite(value)) return value
|
|
366
|
+
return num(machineKey, fallback)
|
|
245
367
|
}
|
|
246
368
|
|
|
247
369
|
let line_width = num('line_width'); if (!line_width) line_width = AUTO_LINE_WIDTH // 0 = auto
|
|
@@ -269,16 +391,21 @@ export function deriveKernelParams(settings, opts) {
|
|
|
269
391
|
// style never reached the kernel and every tree style sliced organic.
|
|
270
392
|
const tree_style = { tree_slim: 'slim', tree_strong: 'strong', tree_hybrid: 'hybrid' }[styleRaw]
|
|
271
393
|
|
|
272
|
-
// Bed: bounding box of the first rectangle in printable_area
|
|
394
|
+
// Bed: bounding box of the first rectangle in printable_area. Its lower-left corner is sent only when it is not
|
|
395
|
+
// (0,0) — a delta bed centred on the origin, or an offset cartesian one — so every bed that starts at the corner
|
|
396
|
+
// keeps exactly the parameters it had (see bedCenter below for what the kernel does with it).
|
|
273
397
|
const pa = settingRaw(settings, 'printable_area')
|
|
274
398
|
let bed_width = BED_FALLBACK.width, bed_depth = BED_FALLBACK.depth
|
|
399
|
+
const bedOriginParams = {} // read back through bedOrigin/bedCenter (bed_frame.js)
|
|
275
400
|
if (Array.isArray(pa) && pa.length) {
|
|
276
401
|
const xs = pa.map(p => p[0]), ys = pa.map(p => p[1])
|
|
277
402
|
bed_width = Math.max(...xs) - Math.min(...xs); bed_depth = Math.max(...ys) - Math.min(...ys)
|
|
403
|
+
if (Math.min(...xs) !== 0) bedOriginParams.bed_origin_x = Math.min(...xs)
|
|
404
|
+
if (Math.min(...ys) !== 0) bedOriginParams.bed_origin_y = Math.min(...ys)
|
|
278
405
|
}
|
|
279
406
|
|
|
280
407
|
// Stage 21: per-feature widths — passed to the kernel verbatim (including strings like "120%"). Unedited keys are omitted -> the kernel derives auto(=line_width).
|
|
281
|
-
// The 0=auto semantics are handled by the kernel's
|
|
408
|
+
// The 0=auto semantics are handled by the kernel's resolve_line_width (upstream Flow formula). Defaults unchanged (no feature set -> line_width).
|
|
282
409
|
const widths = {}
|
|
283
410
|
for (const k of ['outer_wall_line_width','inner_wall_line_width','top_surface_line_width',
|
|
284
411
|
'sparse_infill_line_width','internal_solid_infill_line_width','initial_layer_line_width']) {
|
|
@@ -316,9 +443,11 @@ export function deriveKernelParams(settings, opts) {
|
|
|
316
443
|
// the plate and disabled the automatic placement entirely — measured as a tower at z=-127.5 on a 200mm bed.
|
|
317
444
|
// Absent from the map means nobody chose one, and the placement beside the model stands.
|
|
318
445
|
const chosen = (key) => { const raw = settings?.[key]
|
|
319
|
-
|
|
446
|
+
let v = raw
|
|
447
|
+
if (Array.isArray(raw)) v = raw[plate]
|
|
320
448
|
const n = Number(v)
|
|
321
|
-
|
|
449
|
+
if (v != null && v !== '' && Number.isFinite(n)) return n
|
|
450
|
+
return null }
|
|
322
451
|
const towerX = chosen('wipe_tower_x'), towerY = chosen('wipe_tower_y')
|
|
323
452
|
if (towerX != null) towerSettings.prime_tower_x = towerX
|
|
324
453
|
if (towerY != null) towerSettings.prime_tower_y = towerY
|
|
@@ -356,31 +485,80 @@ export function deriveKernelParams(settings, opts) {
|
|
|
356
485
|
for (const key of PASSTHROUGH_NUM_VECTOR) {
|
|
357
486
|
if (!present(key)) continue
|
|
358
487
|
const raw = settings[key]
|
|
359
|
-
passthrough[key] = (
|
|
488
|
+
passthrough[key] = asList(raw).map(entry => finiteOr(Number(entry), 0))
|
|
360
489
|
}
|
|
361
490
|
for (const key of PASSTHROUGH_STR_VECTOR) {
|
|
362
491
|
if (!present(key)) continue
|
|
363
492
|
const raw = settings[key]
|
|
364
|
-
passthrough[key] = (
|
|
493
|
+
passthrough[key] = asList(raw).map(entry => String(entry ?? ''))
|
|
365
494
|
}
|
|
366
495
|
// support_line_width is coFloatOrPercent, but the kernel reads it with the plain number reader rather than the
|
|
367
|
-
// percent-aware
|
|
496
|
+
// percent-aware json_width_raw the per-feature widths get. A "120%" would reach strtod as a quoted string and
|
|
368
497
|
// parse to 0 == auto — the setting silently ignored. Resolved here against the nozzle, which is the same
|
|
369
|
-
// thing
|
|
498
|
+
// thing json_width_raw does for the widths that do get the percent-aware reader.
|
|
370
499
|
if (present('support_line_width')) {
|
|
371
500
|
const text = String(settingScalar(settings, 'support_line_width')).trim()
|
|
372
|
-
|
|
501
|
+
let width
|
|
502
|
+
if (text.endsWith('%')) width = (parseFloat(text) / 100) * num('nozzle_diameter')
|
|
503
|
+
else width = Number(text)
|
|
373
504
|
if (Number.isFinite(width) && width > 0) passthrough.support_line_width = width
|
|
374
505
|
}
|
|
375
506
|
}
|
|
376
507
|
|
|
508
|
+
const motion = {}
|
|
509
|
+
for (const [key, ratioOver] of MOTION_KEYS) {
|
|
510
|
+
if (!presentOrTemplate(key)) continue
|
|
511
|
+
const raw = settingScalar(settings, key)
|
|
512
|
+
let value = Number(raw)
|
|
513
|
+
// A percent of another option (ratio_over) resolves against it; a plain coPercent (accel_to_decel_factor "50%")
|
|
514
|
+
// is the number itself.
|
|
515
|
+
if (typeof raw === 'string' && raw.trim().endsWith('%')) {
|
|
516
|
+
value = parseFloat(raw)
|
|
517
|
+
if (ratioOver) value = value / 100 * num(ratioOver, 0)
|
|
518
|
+
}
|
|
519
|
+
if (Number.isFinite(value)) motion[key] = value
|
|
520
|
+
}
|
|
521
|
+
for (const key of MOTION_BOOLS) if (presentOrTemplate(key)) motion[key] = bool(key, false)
|
|
522
|
+
|
|
523
|
+
// emit_machine_limits_to_gcode: the machine's limits written into the G-code (upstream print_machine_envelope),
|
|
524
|
+
// as the 16 numbers machine_writer.h reads in this order. Sent only when the profile turns it on.
|
|
525
|
+
const envelope = {}
|
|
526
|
+
if (presentOrTemplate('emit_machine_limits_to_gcode') && bool('emit_machine_limits_to_gcode', false)) {
|
|
527
|
+
envelope.machine_envelope = ['machine_max_acceleration_x', 'machine_max_acceleration_y', 'machine_max_acceleration_z',
|
|
528
|
+
'machine_max_acceleration_e', 'machine_max_speed_x', 'machine_max_speed_y', 'machine_max_speed_z', 'machine_max_speed_e',
|
|
529
|
+
'machine_max_acceleration_extruding', 'machine_max_acceleration_retracting', 'machine_max_acceleration_travel',
|
|
530
|
+
'machine_max_jerk_x', 'machine_max_jerk_y', 'machine_max_jerk_z', 'machine_max_jerk_e', 'machine_max_junction_deviation',
|
|
531
|
+
].map(key => num(key, 0))
|
|
532
|
+
}
|
|
533
|
+
|
|
377
534
|
// Sent only for the three non-organic styles (see tree_style above), so an empty map still derives the same keys.
|
|
378
535
|
const treeStyle = {}
|
|
379
536
|
if (tree_style) treeStyle.tree_style = tree_style
|
|
380
537
|
|
|
538
|
+
// Temperatures. The bed's option follows the plate type upstream does (get_bed_temp_key). The first layer's own
|
|
539
|
+
// temperatures (nozzle_temperature_initial_layer, <plate>_temp_initial_layer) start the print and the kernel
|
|
540
|
+
// switches at the second layer, as upstream's process_layer does (GCode.cpp:5631). With a printer's custom
|
|
541
|
+
// G-code the start block's temperatures come from custom_gcode_bridge, which reads every option with its default,
|
|
542
|
+
// so the kernel takes the first-layer ones the same way or the two would disagree about what the print started
|
|
543
|
+
// at. Without one, only keys the map holds are sent. A map without curr_bed_type is on DEFAULT_BED_TYPE.
|
|
544
|
+
const bedTempKey = BED_TEMP_KEYS[settings?.curr_bed_type ?? DEFAULT_BED_TYPE] ?? 'hot_plate_temp'
|
|
545
|
+
const printerCommands = { bed_temp: num(bedTempKey, FFF_FALLBACKS.hot_plate_temp) }
|
|
546
|
+
if (presentOrTemplate('nozzle_temperature_initial_layer')) printerCommands.first_layer_nozzle_temp = num('nozzle_temperature_initial_layer')
|
|
547
|
+
if (presentOrTemplate(`${bedTempKey}_initial_layer`)) printerCommands.first_layer_bed_temp = num(`${bedTempKey}_initial_layer`, FFF_FALLBACKS.hot_plate_temp)
|
|
548
|
+
// Whether one nozzle carries every filament (upstream single_extruder_multi_material): the second-layer switch
|
|
549
|
+
// then names no tool and sets only the loaded one.
|
|
550
|
+
if (presentOrTemplate('single_extruder_multi_material')) printerCommands.single_extruder_multi_material = bool('single_extruder_multi_material', true)
|
|
551
|
+
// No retraction for a travel inside the infill (upstream GCode::needs_retraction); upstream's default is on.
|
|
552
|
+
if (presentOrTemplate('reduce_infill_retraction')) printerCommands.reduce_infill_retraction = bool('reduce_infill_retraction', true)
|
|
553
|
+
// The firmware flavor formats the machine commands (machine_writer.h); upstream's default is "marlin".
|
|
554
|
+
if (presentOrTemplate('gcode_flavor')) printerCommands.gcode_flavor = str('gcode_flavor')
|
|
555
|
+
// disable_m73 false: the G-code carries upstream's M73 progress placeholders (finalizeGcode fills them).
|
|
556
|
+
if (presentOrTemplate('disable_m73')) printerCommands.disable_m73 = bool('disable_m73', false)
|
|
557
|
+
|
|
381
558
|
const perExtruder = {}
|
|
382
559
|
for (const [param, key, scalar] of [
|
|
383
560
|
['extruder_nozzle_temp', 'nozzle_temperature', num('nozzle_temperature')],
|
|
561
|
+
['extruder_first_layer_temp', 'nozzle_temperature_initial_layer', num('nozzle_temperature_initial_layer')],
|
|
384
562
|
['extruder_filament_diameter', 'filament_diameter', num('filament_diameter')],
|
|
385
563
|
['extruder_flow_ratio', 'filament_flow_ratio', num('filament_flow_ratio')],
|
|
386
564
|
['extruder_retract_length', 'filament_retraction_length', override('filament_retraction_length', 'retraction_length')],
|
|
@@ -391,8 +569,7 @@ export function deriveKernelParams(settings, opts) {
|
|
|
391
569
|
if (!Array.isArray(raw) || raw.length < 2) continue
|
|
392
570
|
perExtruder[param] = raw.map(v => {
|
|
393
571
|
if (v == null || v === '') return scalar // null survives a JSON round-trip; Number(null) would be 0
|
|
394
|
-
|
|
395
|
-
return Number.isFinite(n) ? n : scalar
|
|
572
|
+
return finiteOr(Number(firstEntryOf(v)), scalar)
|
|
396
573
|
})
|
|
397
574
|
}
|
|
398
575
|
|
|
@@ -401,6 +578,8 @@ export function deriveKernelParams(settings, opts) {
|
|
|
401
578
|
...machine,
|
|
402
579
|
...passthrough, // present only for keys the settings map actually holds (see PASSTHROUGH_* above)
|
|
403
580
|
...treeStyle,
|
|
581
|
+
...envelope,
|
|
582
|
+
...motion,
|
|
404
583
|
layer_height: num('layer_height'),
|
|
405
584
|
first_layer_height: num('initial_layer_print_height'),
|
|
406
585
|
line_width,
|
|
@@ -434,14 +613,15 @@ export function deriveKernelParams(settings, opts) {
|
|
|
434
613
|
filament_diameter: num('filament_diameter'),
|
|
435
614
|
flow_ratio: num('filament_flow_ratio'),
|
|
436
615
|
nozzle_temp: num('nozzle_temperature'),
|
|
437
|
-
|
|
616
|
+
...printerCommands, // temperatures (bed by plate type, the first layer's own) and the flavor
|
|
438
617
|
bed_width, bed_depth,
|
|
618
|
+
...bedOriginParams,
|
|
439
619
|
bed_height: num('printable_height'), // 0 = profile states no ceiling -> kernel skips the check
|
|
440
620
|
// Explicitly-set only: the schema default here is upstream's generic "G28 / G1 Z5" preamble, and falling back
|
|
441
621
|
// to it would rewrite the emitted G-code for every caller. The kernel keeps its own preamble when these are empty.
|
|
442
622
|
machine_start_gcode: String(settings?.machine_start_gcode ?? ''),
|
|
443
623
|
machine_end_gcode: String(settings?.machine_end_gcode ?? ''),
|
|
444
|
-
...placeholderConfig(settings, plate),
|
|
624
|
+
...placeholderConfig(settings, plate, opts?.objectNames),
|
|
445
625
|
enable_support: bool('enable_support'),
|
|
446
626
|
support_threshold_angle: num('support_threshold_angle'),
|
|
447
627
|
support_top_z_distance: num('support_top_z_distance'),
|
|
@@ -459,7 +639,7 @@ export function deriveKernelParams(settings, opts) {
|
|
|
459
639
|
support_expansion: num('support_expansion'),
|
|
460
640
|
support_threshold_overlap: (() => { const v = settingRaw(settings, 'support_threshold_overlap')
|
|
461
641
|
if (typeof v === 'string' && v.trim().endsWith('%')) return parseFloat(v) / 100
|
|
462
|
-
|
|
642
|
+
return finiteOr(Number(v), 0.5) })(), // "50%" -> 0.5 (ratio of extrusion width)
|
|
463
643
|
support_on_build_plate_only: bool('support_on_build_plate_only'),
|
|
464
644
|
support_interface_bottom_layers: num('support_interface_bottom_layers'),
|
|
465
645
|
...supportTools, // present only when a support extruder is mapped (see above)
|
|
@@ -487,7 +667,7 @@ export function deriveKernelParams(settings, opts) {
|
|
|
487
667
|
reduce_crossing_wall: bool('reduce_crossing_wall'),
|
|
488
668
|
max_volumetric_extrusion_rate_slope: num('max_volumetric_extrusion_rate_slope'),
|
|
489
669
|
// Stage 7: wall generator classic|arachne (arachne = the real ported OrcaSlicer WallToolPaths, variable width)
|
|
490
|
-
wall_generator: (str('wall_generator')
|
|
670
|
+
wall_generator: allowedOr(str('wall_generator'), ['arachne'], 'classic'),
|
|
491
671
|
// support_density has no matching schema key, so the kernel default (0.15) is used; scarf_length likewise uses the kernel default (10mm)
|
|
492
672
|
}
|
|
493
673
|
}
|
|
@@ -498,7 +678,8 @@ export function deriveKernelParams(settings, opts) {
|
|
|
498
678
|
// path runs. The schema already carries the key (inherited from the upstream lineage), so an imported preset
|
|
499
679
|
// that names a resin printer routes itself.
|
|
500
680
|
export function printerTechnology(settings) {
|
|
501
|
-
|
|
681
|
+
if (String(settingRaw(settings, 'printer_technology') ?? 'FFF').toUpperCase() === 'SLA') return 'SLA'
|
|
682
|
+
return 'FFF'
|
|
502
683
|
}
|
|
503
684
|
|
|
504
685
|
// SLA settings provenance table. Rows come from the schema's `defined_in` marker so a new resin option cannot
|
|
@@ -513,13 +694,12 @@ const SLA_SETTING_OVERRIDES = {
|
|
|
513
694
|
|
|
514
695
|
const SLA_SCHEMA_ROWS = Object.fromEntries(Object.entries(schema)
|
|
515
696
|
.filter(([key, meta]) => key === 'printer_technology' || String(meta.defined_in ?? '').includes('sla_'))
|
|
516
|
-
.map(([key, meta]) =>
|
|
517
|
-
source: 'config-schema.json',
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}]))
|
|
697
|
+
.map(([key, meta]) => {
|
|
698
|
+
const row = { source: 'config-schema.json', omission: 'schema-default', default: meta.default }
|
|
699
|
+
if (meta.enum_values) row.enum = [...meta.enum_values]
|
|
700
|
+
row.provenance = `${meta.defined_in}:${meta.line}`
|
|
701
|
+
return [key, row]
|
|
702
|
+
}))
|
|
523
703
|
|
|
524
704
|
export const SLA_SETTING_CONTRACT = Object.freeze(Object.fromEntries(
|
|
525
705
|
Object.entries({ ...SLA_SCHEMA_ROWS, ...SLA_SETTING_OVERRIDES }).map(([key, row]) => [key, Object.freeze({
|
|
@@ -535,27 +715,40 @@ export const slaSettingKeys = Object.freeze(Object.keys(SLA_SETTING_CONTRACT))
|
|
|
535
715
|
// and its empty-map key count is a pinned invariant — this one feeds the JS contour slicer and owes bytes to
|
|
536
716
|
// nobody yet. Keys the schema does not define (the resin display's pixel grid, the per-layer height override)
|
|
537
717
|
// are read from the map directly, so they follow the same omission-friendly rule: absent means the default.
|
|
718
|
+
// The number when it is finite and above zero, otherwise the fallback.
|
|
719
|
+
function positiveOr(number, fallback) {
|
|
720
|
+
if (Number.isFinite(number) && number > 0) return number
|
|
721
|
+
return fallback
|
|
722
|
+
}
|
|
723
|
+
// The number when it is finite and not negative (zero is legal), otherwise the fallback.
|
|
724
|
+
function nonNegativeOr(number, fallback) {
|
|
725
|
+
if (Number.isFinite(number) && number >= 0) return number
|
|
726
|
+
return fallback
|
|
727
|
+
}
|
|
728
|
+
// A flag whose absence means on.
|
|
729
|
+
function trueUnlessSet(value) {
|
|
730
|
+
if (value === undefined) return true
|
|
731
|
+
return !!value
|
|
732
|
+
}
|
|
538
733
|
export function deriveSlaParams(settings) {
|
|
539
734
|
const S = k => settingScalar(settings, k)
|
|
540
|
-
const num = (k, d = SLA_FALLBACKS[k]) =>
|
|
735
|
+
const num = (k, d = SLA_FALLBACKS[k]) => positiveOr(Number(S(k)), d)
|
|
541
736
|
|
|
542
737
|
// Read from the MAP, not through the schema fallback: the key is shared with FFF and its schema default is that
|
|
543
738
|
// technology's opinion (0.2mm) — four resin layers thick. Absent means the resin default here.
|
|
544
739
|
const rawLayerHeight = settings?.layer_height
|
|
545
|
-
const mapLayerHeight = Number(
|
|
546
|
-
const layer_height =
|
|
740
|
+
const mapLayerHeight = Number(firstEntryOf(rawLayerHeight))
|
|
741
|
+
const layer_height = positiveOr(mapLayerHeight, SLA_FALLBACKS.layer_height)
|
|
547
742
|
// Zero is a legal fade count (no fade band), so this one cannot go through the positives-only reader.
|
|
548
743
|
const fadedRaw = Number(S('faded_layers'))
|
|
549
744
|
const requestedSupportTree = String(S('support_tree_type') ?? 'default').toLowerCase()
|
|
550
|
-
const support_tree_type = ['default', 'branching', 'organic']
|
|
551
|
-
? requestedSupportTree
|
|
552
|
-
: 'default'
|
|
745
|
+
const support_tree_type = allowedOr(requestedSupportTree, ['default', 'branching', 'organic'], 'default')
|
|
553
746
|
return {
|
|
554
747
|
layer_height,
|
|
555
748
|
initial_layer_height: num('initial_layer_height', layer_height),
|
|
556
749
|
exposure_time: num('exposure_time'),
|
|
557
750
|
initial_exposure_time: num('initial_exposure_time'),
|
|
558
|
-
faded_layers:
|
|
751
|
+
faded_layers: nonNegativeOr(fadedRaw, 10),
|
|
559
752
|
// The physical display and its pixel grid — what turns millimetres into raster pixels at export time.
|
|
560
753
|
// Fallbacks are the SL1's own panel, the reference machine of the format this exports to.
|
|
561
754
|
display_width: num('display_width'),
|
|
@@ -564,8 +757,8 @@ export function deriveSlaParams(settings) {
|
|
|
564
757
|
display_pixels_y: num('display_pixels_y'),
|
|
565
758
|
// Panel mounting: the whole SL1 family is portrait with X mirrored (projection through the vat floor);
|
|
566
759
|
// the raster writer swaps the image axes for portrait exactly like upstream SL1.cpp create_raster.
|
|
567
|
-
display_orientation: String(S('display_orientation') ?? 'portrait')
|
|
568
|
-
display_mirror_x:
|
|
760
|
+
display_orientation: allowedOr(String(S('display_orientation') ?? 'portrait'), ['landscape'], 'portrait'),
|
|
761
|
+
display_mirror_x: trueUnlessSet(settingRaw(settings, 'display_mirror_x')),
|
|
569
762
|
display_mirror_y: !!settingRaw(settings, 'display_mirror_y'),
|
|
570
763
|
// SL1 archive identity fields (config.ini): written as-is, empty when the host never set a named
|
|
571
764
|
// profile — the same thing upstream produces then. material_print_speed defaults to 'fast', the value
|
|
@@ -606,23 +799,23 @@ export function deriveSlaParams(settings) {
|
|
|
606
799
|
// percent sign is stripped here; widening factor and base safety distance are legally ZERO (zero safety
|
|
607
800
|
// falls back to the kernel's own safety_distance), so neither can go through the positives-only reader.
|
|
608
801
|
support_small_pillar_diameter_percent:
|
|
609
|
-
(
|
|
802
|
+
positiveOr(Number(String(S('support_small_pillar_diameter_percent') ?? '').replace('%', '')), 50),
|
|
610
803
|
support_pillar_widening_factor:
|
|
611
|
-
(
|
|
804
|
+
nonNegativeOr(Number(S('support_pillar_widening_factor')), 0),
|
|
612
805
|
support_base_safety_distance:
|
|
613
|
-
(
|
|
806
|
+
nonNegativeOr(Number(S('support_base_safety_distance')), 1),
|
|
614
807
|
support_max_bridges_on_pillar: num('support_max_bridges_on_pillar'),
|
|
615
808
|
support_max_weight_on_model: num('support_max_weight_on_model'),
|
|
616
809
|
// The generator's input slices are gap-closed with this radius, exactly like upstream slice_mesh_ex.
|
|
617
810
|
slice_closing_radius: num('slice_closing_radius'),
|
|
618
|
-
support_object_elevation: (
|
|
811
|
+
support_object_elevation: nonNegativeOr(Number(S('support_object_elevation')), 5),
|
|
619
812
|
support_pillar_connection_mode: String(S('support_pillar_connection_mode') ?? 'dynamic'),
|
|
620
813
|
// The pad (upstream make_pad_cfg): wall height is legally ZERO (flat pad, no cavity), so it cannot go
|
|
621
814
|
// through the positives-only reader.
|
|
622
815
|
pad_brim_size: num('pad_brim_size'),
|
|
623
816
|
pad_wall_thickness: num('pad_wall_thickness'),
|
|
624
817
|
pad_wall_height:
|
|
625
|
-
(
|
|
818
|
+
nonNegativeOr(Number(S('pad_wall_height')), 0),
|
|
626
819
|
pad_wall_slope: num('pad_wall_slope'),
|
|
627
820
|
pad_max_merge_distance: num('pad_max_merge_distance'),
|
|
628
821
|
pad_around_object: !!settingRaw(settings, 'pad_around_object'),
|
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
|
package/types/settings.d.ts
CHANGED
|
@@ -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
|
package/types/toolpath.d.ts
CHANGED
|
@@ -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
|