three-slicer-viewer 0.3.2 → 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/TOOLPATH_SPEC.md +8 -1
- package/VIEWER.md +24 -0
- package/data/config-schema-lean.json +1 -1
- package/dist/components.js +47 -43
- package/dist/gcode_parse.js +154 -111
- package/dist/index.js +4409 -4069
- package/dist/{model_loaders-DxlFNFOu.js → model_loaders-Dk9rqXFb.js} +27 -27
- package/dist/model_loaders.js +2 -2
- package/dist/parse_3mf-CpbA-6Iq.js +666 -0
- package/dist/parse_3mf.worker.js +67 -5
- 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 +303 -177
- package/package.json +6 -1
- package/src/core/paint_tools.js +8 -0
- package/src/settings/bed_frame.js +28 -0
- package/src/settings/index.js +1 -0
- package/src/settings/kernel_setting_keys.js +79 -0
- package/src/settings/settings_core.js +194 -8
- package/types/gcode.d.ts +13 -0
- package/types/paint.d.ts +7 -0
- package/types/settings.d.ts +14 -1
- package/types/toolpath.d.ts +6 -0
- package/types/viewer.d.ts +1 -1
- package/dist/parse_3mf-BZISF3eW.js +0 -327
- package/dist/sl1_write-BovHbg3k.js +0 -303
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The painting tools that act on one click instead of a drag (upstream's Smart fill, Bucket fill and the single-facet
|
|
2
|
+
// POINTER). Read by the brush input, both brush panels and the slicer worker (`three-slicer-viewer/paint`), so a
|
|
3
|
+
// tool added here is one the whole paint path treats as a fill.
|
|
4
|
+
export const FILL_TOOLS = new Set(['smart', 'bucket', 'triangle'])
|
|
5
|
+
|
|
6
|
+
// The fills that spread across neighbouring facets and so take a fill angle. Triangle is bucket fill with the
|
|
7
|
+
// propagation off (the worker passes -1 as its angle), so it has no angle to set.
|
|
8
|
+
export const ANGLE_FILL_TOOLS = new Set(['smart', 'bucket'])
|
|
@@ -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
|
+
}
|
package/src/settings/index.js
CHANGED
|
@@ -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,14 +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",
|
|
6
|
+
"alternate_extra_wall",
|
|
7
|
+
"before_layer_change_gcode",
|
|
4
8
|
"bottom_shell_layers",
|
|
9
|
+
"bottom_solid_infill_flow_ratio",
|
|
5
10
|
"bottom_surface_filament_id",
|
|
11
|
+
"bridge_acceleration",
|
|
12
|
+
"bridge_flow",
|
|
6
13
|
"bridge_no_support",
|
|
7
14
|
"bridge_speed",
|
|
15
|
+
"brim_flow_ratio",
|
|
8
16
|
"brim_object_gap",
|
|
9
17
|
"brim_width",
|
|
18
|
+
"change_filament_gcode",
|
|
10
19
|
"close_fan_the_first_x_layers",
|
|
20
|
+
"curr_bed_type",
|
|
11
21
|
"default_acceleration",
|
|
22
|
+
"default_jerk",
|
|
23
|
+
"detect_thin_wall",
|
|
24
|
+
"disable_m73",
|
|
25
|
+
"emit_machine_limits_to_gcode",
|
|
12
26
|
"enable_arc_fitting",
|
|
13
27
|
"enable_pressure_advance",
|
|
14
28
|
"enable_prime_tower",
|
|
@@ -17,41 +31,71 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
17
31
|
"filament_cost",
|
|
18
32
|
"filament_density",
|
|
19
33
|
"filament_diameter",
|
|
34
|
+
"filament_end_gcode",
|
|
20
35
|
"filament_flow_ratio",
|
|
21
36
|
"filament_map",
|
|
37
|
+
"filament_max_volumetric_speed",
|
|
22
38
|
"filament_retraction_length",
|
|
23
39
|
"filament_retraction_minimum_travel",
|
|
24
40
|
"filament_retraction_speed",
|
|
25
41
|
"filament_settings_id",
|
|
42
|
+
"filament_start_gcode",
|
|
26
43
|
"filament_type",
|
|
27
44
|
"filament_z_hop",
|
|
45
|
+
"file_start_gcode",
|
|
46
|
+
"filter_out_gap_fill",
|
|
47
|
+
"first_layer_flow_ratio",
|
|
28
48
|
"flush_into_infill",
|
|
29
49
|
"flush_multiplier",
|
|
30
50
|
"flush_volumes_matrix",
|
|
31
51
|
"full_fan_speed_layer",
|
|
52
|
+
"gap_fill_flow_ratio",
|
|
53
|
+
"gap_infill_speed",
|
|
54
|
+
"gcode_flavor",
|
|
32
55
|
"hot_plate_temp",
|
|
56
|
+
"hot_plate_temp_initial_layer",
|
|
33
57
|
"independent_support_layer_height",
|
|
34
58
|
"infill_direction",
|
|
59
|
+
"infill_jerk",
|
|
60
|
+
"infill_wall_overlap",
|
|
61
|
+
"initial_layer_acceleration",
|
|
62
|
+
"initial_layer_infill_speed",
|
|
63
|
+
"initial_layer_jerk",
|
|
35
64
|
"initial_layer_line_width",
|
|
36
65
|
"initial_layer_print_height",
|
|
37
66
|
"initial_layer_speed",
|
|
67
|
+
"initial_layer_travel_acceleration",
|
|
68
|
+
"initial_layer_travel_jerk",
|
|
69
|
+
"initial_layer_travel_speed",
|
|
70
|
+
"inner_wall_acceleration",
|
|
38
71
|
"inner_wall_filament_id",
|
|
72
|
+
"inner_wall_flow_ratio",
|
|
73
|
+
"inner_wall_jerk",
|
|
39
74
|
"inner_wall_line_width",
|
|
75
|
+
"inner_wall_speed",
|
|
40
76
|
"internal_solid_filament_id",
|
|
77
|
+
"internal_solid_infill_acceleration",
|
|
78
|
+
"internal_solid_infill_flow_ratio",
|
|
41
79
|
"internal_solid_infill_line_width",
|
|
80
|
+
"internal_solid_infill_speed",
|
|
42
81
|
"ironing_flow",
|
|
43
82
|
"ironing_spacing",
|
|
44
83
|
"ironing_speed",
|
|
45
84
|
"ironing_type",
|
|
85
|
+
"layer_change_gcode",
|
|
46
86
|
"layer_height",
|
|
47
87
|
"line_width",
|
|
48
88
|
"machine_end_gcode",
|
|
49
89
|
"machine_max_acceleration_e",
|
|
90
|
+
"machine_max_acceleration_extruding",
|
|
50
91
|
"machine_max_acceleration_retracting",
|
|
92
|
+
"machine_max_acceleration_travel",
|
|
51
93
|
"machine_max_acceleration_x",
|
|
94
|
+
"machine_max_acceleration_y",
|
|
52
95
|
"machine_max_acceleration_z",
|
|
53
96
|
"machine_max_jerk_e",
|
|
54
97
|
"machine_max_jerk_x",
|
|
98
|
+
"machine_max_jerk_y",
|
|
55
99
|
"machine_max_jerk_z",
|
|
56
100
|
"machine_max_speed_e",
|
|
57
101
|
"machine_max_speed_x",
|
|
@@ -60,32 +104,50 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
60
104
|
"max_volumetric_extrusion_rate_slope",
|
|
61
105
|
"nozzle_diameter",
|
|
62
106
|
"nozzle_temperature",
|
|
107
|
+
"nozzle_temperature_initial_layer",
|
|
108
|
+
"only_one_wall_first_layer",
|
|
109
|
+
"outer_wall_acceleration",
|
|
63
110
|
"outer_wall_filament_id",
|
|
111
|
+
"outer_wall_flow_ratio",
|
|
112
|
+
"outer_wall_jerk",
|
|
64
113
|
"outer_wall_line_width",
|
|
65
114
|
"outer_wall_speed",
|
|
115
|
+
"precise_outer_wall",
|
|
66
116
|
"pressure_advance",
|
|
67
117
|
"prime_tower_width",
|
|
68
118
|
"prime_volume",
|
|
119
|
+
"print_flow_ratio",
|
|
69
120
|
"printable_area",
|
|
70
121
|
"printable_height",
|
|
71
122
|
"raft_contact_distance",
|
|
72
123
|
"raft_expansion",
|
|
73
124
|
"raft_layers",
|
|
74
125
|
"reduce_crossing_wall",
|
|
126
|
+
"reduce_infill_retraction",
|
|
75
127
|
"resolution",
|
|
76
128
|
"retraction_length",
|
|
77
129
|
"retraction_minimum_travel",
|
|
78
130
|
"retraction_speed",
|
|
131
|
+
"scarf_joint_flow_ratio",
|
|
79
132
|
"seam_position",
|
|
80
133
|
"seam_slope_type",
|
|
134
|
+
"set_other_flow_ratios",
|
|
135
|
+
"single_extruder_multi_material",
|
|
81
136
|
"skirt_distance",
|
|
82
137
|
"skirt_height",
|
|
83
138
|
"skirt_loops",
|
|
139
|
+
"skirt_speed",
|
|
84
140
|
"slow_down_layer_time",
|
|
141
|
+
"slow_down_layers",
|
|
142
|
+
"small_perimeter_speed",
|
|
143
|
+
"small_perimeter_threshold",
|
|
144
|
+
"sparse_infill_acceleration",
|
|
85
145
|
"sparse_infill_density",
|
|
86
146
|
"sparse_infill_filament_id",
|
|
147
|
+
"sparse_infill_flow_ratio",
|
|
87
148
|
"sparse_infill_line_width",
|
|
88
149
|
"sparse_infill_pattern",
|
|
150
|
+
"sparse_infill_speed",
|
|
89
151
|
"spiral_mode",
|
|
90
152
|
"support_angle",
|
|
91
153
|
"support_base_pattern",
|
|
@@ -93,25 +155,37 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
93
155
|
"support_bottom_z_distance",
|
|
94
156
|
"support_expansion",
|
|
95
157
|
"support_filament",
|
|
158
|
+
"support_flow_ratio",
|
|
96
159
|
"support_interface_bottom_layers",
|
|
97
160
|
"support_interface_filament",
|
|
161
|
+
"support_interface_flow_ratio",
|
|
98
162
|
"support_interface_pattern",
|
|
99
163
|
"support_interface_spacing",
|
|
164
|
+
"support_interface_speed",
|
|
100
165
|
"support_interface_top_layers",
|
|
101
166
|
"support_line_width",
|
|
102
167
|
"support_object_first_layer_gap",
|
|
103
168
|
"support_object_xy_distance",
|
|
104
169
|
"support_on_build_plate_only",
|
|
105
170
|
"support_remove_small_overhang",
|
|
171
|
+
"support_speed",
|
|
106
172
|
"support_style",
|
|
107
173
|
"support_threshold_angle",
|
|
108
174
|
"support_threshold_overlap",
|
|
109
175
|
"support_top_z_distance",
|
|
110
176
|
"support_type",
|
|
177
|
+
"thick_bridges",
|
|
178
|
+
"time_lapse_gcode",
|
|
179
|
+
"top_bottom_infill_wall_overlap",
|
|
111
180
|
"top_shell_layers",
|
|
181
|
+
"top_solid_infill_flow_ratio",
|
|
182
|
+
"top_surface_acceleration",
|
|
112
183
|
"top_surface_filament_id",
|
|
184
|
+
"top_surface_jerk",
|
|
113
185
|
"top_surface_line_width",
|
|
186
|
+
"top_surface_speed",
|
|
114
187
|
"travel_acceleration",
|
|
188
|
+
"travel_jerk",
|
|
115
189
|
"travel_speed",
|
|
116
190
|
"tree_support_angle_slow",
|
|
117
191
|
"tree_support_branch_angle",
|
|
@@ -124,9 +198,14 @@ export const KERNEL_SETTING_KEYS = [
|
|
|
124
198
|
"tree_support_tip_diameter",
|
|
125
199
|
"tree_support_top_rate",
|
|
126
200
|
"tree_support_wall_count",
|
|
201
|
+
"use_firmware_retraction",
|
|
202
|
+
"use_relative_e_distances",
|
|
203
|
+
"wall_direction",
|
|
127
204
|
"wall_generator",
|
|
128
205
|
"wall_loops",
|
|
206
|
+
"wall_sequence",
|
|
129
207
|
"wipe_tower_x",
|
|
130
208
|
"wipe_tower_y",
|
|
131
209
|
"z_hop",
|
|
210
|
+
"z_offset",
|
|
132
211
|
]
|
|
@@ -59,12 +59,55 @@ const PASSTHROUGH_NUM = [
|
|
|
59
59
|
// rather than silently dropping them.
|
|
60
60
|
'outer_wall_filament_id', 'inner_wall_filament_id', 'sparse_infill_filament_id',
|
|
61
61
|
'top_surface_filament_id', 'bottom_surface_filament_id', 'internal_solid_filament_id',
|
|
62
|
+
// Read by the kernel's classic wall generator (wall_generator = classic).
|
|
63
|
+
'filter_out_gap_fill',
|
|
64
|
+
// Upstream's flow multipliers (GCode.cpp:7343-7390): the print-wide one and each role's own (emit.cpp role_flow_ratio).
|
|
65
|
+
'print_flow_ratio', 'top_solid_infill_flow_ratio', 'bottom_solid_infill_flow_ratio', 'bridge_flow', 'brim_flow_ratio',
|
|
66
|
+
'scarf_joint_flow_ratio', 'outer_wall_flow_ratio', 'inner_wall_flow_ratio', 'sparse_infill_flow_ratio',
|
|
67
|
+
'internal_solid_infill_flow_ratio', 'gap_fill_flow_ratio', 'support_flow_ratio', 'support_interface_flow_ratio',
|
|
68
|
+
'first_layer_flow_ratio',
|
|
69
|
+
// Added to every Z the G-code writes (upstream's GCodeWriter).
|
|
70
|
+
'z_offset',
|
|
62
71
|
]
|
|
63
|
-
const PASSTHROUGH_BOOL = ['independent_support_layer_height'
|
|
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',
|
|
75
|
+
'detect_thin_wall', 'precise_outer_wall', 'only_one_wall_first_layer', 'alternate_extra_wall',
|
|
76
|
+
// thick_bridges picks upstream's round bridge thread; set_other_flow_ratios turns the per-role ratios on.
|
|
77
|
+
'thick_bridges', 'set_other_flow_ratios']
|
|
78
|
+
// coPercent options. A 3mf import delivers "15%" while the schema default is the bare 15, so the sign is stripped and
|
|
79
|
+
// the kernel gets the number of percent either way.
|
|
80
|
+
const PASSTHROUGH_PERCENT = ['infill_wall_overlap', 'top_bottom_infill_wall_overlap']
|
|
81
|
+
// coEnum options the kernel reads by their upstream spelling.
|
|
82
|
+
const PASSTHROUGH_STR = ['wall_sequence', 'wall_direction']
|
|
64
83
|
// Per-filament vectors, one entry per filament, passed positionally like every other per-extruder array.
|
|
65
84
|
// filament_map is which physical extruder each filament sits in; density and cost turn extruded millimetres into
|
|
66
|
-
// the grams and currency the G-code footer reports.
|
|
67
|
-
|
|
85
|
+
// the grams and currency the G-code footer reports. gap_infill_speed is per extruder rather than per filament; the
|
|
86
|
+
// kernel reads its first entry, the wall filament's on the single-material path. filament_max_volumetric_speed caps
|
|
87
|
+
// every extrusion's speed as upstream does (GCode.cpp:7492).
|
|
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
|
+
|
|
68
111
|
// Material identity. The kernel needs the type for the two decisions upstream makes by material name (PETG's
|
|
69
112
|
// extra unretract, TPU on the first layer) and the settings id for the footer.
|
|
70
113
|
const PASSTHROUGH_STR_VECTOR = ['filament_type', 'filament_settings_id']
|
|
@@ -192,11 +235,82 @@ export function serializeProjectSettings(settings) {
|
|
|
192
235
|
}
|
|
193
236
|
|
|
194
237
|
|
|
238
|
+
// Issue 63: the custom G-code the kernel expands with upstream's PlaceholderParser (custom_gcode.cpp). The kernel
|
|
239
|
+
// gets the flattened settings it expands them against only when one of these holds text, which keeps every caller
|
|
240
|
+
// without a template on the raw path, byte-identical to before. The settings go in the form a 3mf's
|
|
241
|
+
// project_settings.config stores (serializeProjectSettings), as one JSON string: the kernel's flat key search
|
|
242
|
+
// never looks inside a string value, so no key in it can be taken for a kernel parameter the map omits.
|
|
243
|
+
// The kernel names the same templates in custom_gcode.cpp; test_custom_gcode.mjs checks each key here is expanded there.
|
|
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']
|
|
246
|
+
|
|
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) {
|
|
250
|
+
const hasText = key => [settings?.[key]].flat().some(value => String(value ?? '').trim() !== '')
|
|
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 {}
|
|
280
|
+
// upstream's Print::get_plate_number_formatted: the 1-based plate, zero-padded to two digits
|
|
281
|
+
const plateNumber = String(plate + 1).padStart(2, '0')
|
|
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',
|
|
301
|
+
}
|
|
302
|
+
|
|
195
303
|
// Right-panel settings values -> kernel parameters (derived from schema keys)
|
|
196
304
|
// opts.plate: which plate's entry to take from per-plate array options (wipe_tower_x/y — upstream coFloats,
|
|
197
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).
|
|
198
307
|
export function deriveKernelParams(settings, opts) {
|
|
199
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] !== '')
|
|
200
314
|
const S = k => settingScalar(settings, k)
|
|
201
315
|
const str = k => String(S(k) ?? FFF_FALLBACKS[k])
|
|
202
316
|
const num = (k, d = FFF_FALLBACKS[k]) => { const v = Number(S(k)); return Number.isFinite(v) ? v : d }
|
|
@@ -228,15 +342,25 @@ export function deriveKernelParams(settings, opts) {
|
|
|
228
342
|
// the default style silently sliced grid. An explicit non-tree style keeps winning over the type.
|
|
229
343
|
const styleRaw = str('support_style')
|
|
230
344
|
const typeRaw = str('support_type')
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
//
|
|
345
|
+
let support_style = 'grid'
|
|
346
|
+
if (/tree|organic/i.test(styleRaw) || (/tree/i.test(typeRaw) && styleRaw === 'default')) support_style = 'tree'
|
|
347
|
+
// Which tree. Upstream's slim / strong / hybrid run TreeSupport and organic runs TreeSupport3D
|
|
348
|
+
// (SupportParameters.hpp:190-204), and a tree type left on 'default' resolves to organic there. The kernel defaults to
|
|
349
|
+
// organic too, so only the three others are sent: an organic slice keeps exactly the parameters it had. Before this the
|
|
350
|
+
// style never reached the kernel and every tree style sliced organic.
|
|
351
|
+
const tree_style = { tree_slim: 'slim', tree_strong: 'strong', tree_hybrid: 'hybrid' }[styleRaw]
|
|
352
|
+
|
|
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).
|
|
235
356
|
const pa = settingRaw(settings, 'printable_area')
|
|
236
357
|
let bed_width = BED_FALLBACK.width, bed_depth = BED_FALLBACK.depth
|
|
358
|
+
const bedOriginParams = {} // read back through bedOrigin/bedCenter (bed_frame.js)
|
|
237
359
|
if (Array.isArray(pa) && pa.length) {
|
|
238
360
|
const xs = pa.map(p => p[0]), ys = pa.map(p => p[1])
|
|
239
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)
|
|
240
364
|
}
|
|
241
365
|
|
|
242
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).
|
|
@@ -309,6 +433,12 @@ export function deriveKernelParams(settings, opts) {
|
|
|
309
433
|
if (Number.isFinite(value)) passthrough[key] = value
|
|
310
434
|
}
|
|
311
435
|
for (const key of PASSTHROUGH_BOOL) if (present(key)) passthrough[key] = bool(key, false)
|
|
436
|
+
for (const key of PASSTHROUGH_PERCENT) {
|
|
437
|
+
if (!present(key)) continue
|
|
438
|
+
const value = Number(String(S(key)).replace('%', ''))
|
|
439
|
+
if (Number.isFinite(value)) passthrough[key] = value
|
|
440
|
+
}
|
|
441
|
+
for (const key of PASSTHROUGH_STR) if (present(key)) passthrough[key] = String(S(key))
|
|
312
442
|
for (const key of PASSTHROUGH_NUM_VECTOR) {
|
|
313
443
|
if (!present(key)) continue
|
|
314
444
|
const raw = settings[key]
|
|
@@ -330,9 +460,60 @@ export function deriveKernelParams(settings, opts) {
|
|
|
330
460
|
}
|
|
331
461
|
}
|
|
332
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
|
+
|
|
489
|
+
// Sent only for the three non-organic styles (see tree_style above), so an empty map still derives the same keys.
|
|
490
|
+
const treeStyle = {}
|
|
491
|
+
if (tree_style) treeStyle.tree_style = tree_style
|
|
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
|
+
|
|
333
513
|
const perExtruder = {}
|
|
334
514
|
for (const [param, key, scalar] of [
|
|
335
515
|
['extruder_nozzle_temp', 'nozzle_temperature', num('nozzle_temperature')],
|
|
516
|
+
['extruder_first_layer_temp', 'nozzle_temperature_initial_layer', num('nozzle_temperature_initial_layer')],
|
|
336
517
|
['extruder_filament_diameter', 'filament_diameter', num('filament_diameter')],
|
|
337
518
|
['extruder_flow_ratio', 'filament_flow_ratio', num('filament_flow_ratio')],
|
|
338
519
|
['extruder_retract_length', 'filament_retraction_length', override('filament_retraction_length', 'retraction_length')],
|
|
@@ -352,6 +533,9 @@ export function deriveKernelParams(settings, opts) {
|
|
|
352
533
|
...widths,
|
|
353
534
|
...machine,
|
|
354
535
|
...passthrough, // present only for keys the settings map actually holds (see PASSTHROUGH_* above)
|
|
536
|
+
...treeStyle,
|
|
537
|
+
...envelope,
|
|
538
|
+
...motion,
|
|
355
539
|
layer_height: num('layer_height'),
|
|
356
540
|
first_layer_height: num('initial_layer_print_height'),
|
|
357
541
|
line_width,
|
|
@@ -385,13 +569,15 @@ export function deriveKernelParams(settings, opts) {
|
|
|
385
569
|
filament_diameter: num('filament_diameter'),
|
|
386
570
|
flow_ratio: num('filament_flow_ratio'),
|
|
387
571
|
nozzle_temp: num('nozzle_temperature'),
|
|
388
|
-
|
|
572
|
+
...printerCommands, // temperatures (bed by plate type, the first layer's own) and the flavor
|
|
389
573
|
bed_width, bed_depth,
|
|
574
|
+
...bedOriginParams,
|
|
390
575
|
bed_height: num('printable_height'), // 0 = profile states no ceiling -> kernel skips the check
|
|
391
576
|
// Explicitly-set only: the schema default here is upstream's generic "G28 / G1 Z5" preamble, and falling back
|
|
392
577
|
// to it would rewrite the emitted G-code for every caller. The kernel keeps its own preamble when these are empty.
|
|
393
578
|
machine_start_gcode: String(settings?.machine_start_gcode ?? ''),
|
|
394
579
|
machine_end_gcode: String(settings?.machine_end_gcode ?? ''),
|
|
580
|
+
...placeholderConfig(settings, plate, opts?.objectNames),
|
|
395
581
|
enable_support: bool('enable_support'),
|
|
396
582
|
support_threshold_angle: num('support_threshold_angle'),
|
|
397
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
|
package/types/paint.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// three-slicer-viewer/paint — the painting tool names the viewer and the slicer worker share.
|
|
2
|
+
|
|
3
|
+
/** The tools that fill on one click rather than paint along a drag: 'smart', 'bucket', 'triangle'. */
|
|
4
|
+
export const FILL_TOOLS: ReadonlySet<string>
|
|
5
|
+
|
|
6
|
+
/** The fills that spread across neighbouring facets and take a fill angle: 'smart', 'bucket'. */
|
|
7
|
+
export const ANGLE_FILL_TOOLS: ReadonlySet<string>
|
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
|
package/types/viewer.d.ts
CHANGED
|
@@ -169,7 +169,7 @@ export interface ViewportProps {
|
|
|
169
169
|
* way: OrbitControls preventDefaults `contextmenu` itself.
|
|
170
170
|
* - `logs` — console diagnostics, from the component and from the slice worker.
|
|
171
171
|
*/
|
|
172
|
-
export type ViewportFeature = 'shortcuts' | 'warmup' | 'drop' | 'filePicker' | 'contextMenu' | 'logs'
|
|
172
|
+
export type ViewportFeature = 'shortcuts' | 'warmup' | 'drop' | 'filePicker' | 'contextMenu' | 'logs' | 'versionBadge'
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
175
|
* The panels that accept `'readonly'` as well as `true`/`false` — the right column and the cards in it. Everything
|