three-slicer 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/engine/PARAMS.md CHANGED
@@ -8,7 +8,7 @@ cannot drift from what `slice()` actually honours. Regenerate with `node types/g
8
8
 
9
9
  <!-- KERNEL-PARAMS:BEGIN — generated by types/gen_kernel_params.mjs, do not edit by hand -->
10
10
 
11
- The kernel accepts **160 parameters**. `deriveKernelParams()` produces 131 of them from the
11
+ The kernel accepts **238 parameters**. `deriveKernelParams()` produces 209 of them from the
12
12
  OrcaSlicer settings map; the remaining 29 have no schema key behind them and can only be set by writing
13
13
  the params object directly — those rows show `—` in the *From setting* column.
14
14
 
@@ -46,19 +46,24 @@ same unit — `first_layer_height` comes from `initial_layer_print_height`, and
46
46
  | `bottom_shell_layers` | `number` | `3` | `bottom_shell_layers` | top_shell_layers/bottom_shell_layers |
47
47
  | `infill_angle` | `number` | `45.0` | `infill_direction` | infill_direction |
48
48
  | `infill_density` | `number` | `0.15` | `sparse_infill_density` | |
49
+ | `infill_jerk` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `infill_jerk`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
50
+ | `infill_wall_overlap` | `number` | `15.0` | `infill_wall_overlap` | infill_wall_overlap (percent) |
49
51
  | `ironing_flow` | `number` | `10.0` | `ironing_flow` | ironing flow (%) |
50
52
  | `ironing_spacing` | `number` | `0.1` | `ironing_spacing` | ironing line spacing (mm) |
51
53
  | `ironing_speed` | `number` | `30.0` | `ironing_speed` | ironing speed (mm/s) |
52
54
  | `ironing_type` | `string` | `'none'` | `ironing_type` | none\|top\|topmost\|solid (the top family = on) |
55
+ | `sparse_infill_acceleration` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `sparse_infill_acceleration`, `time_lapse_gcode` | |
53
56
  | `sparse_infill_filament_id` | `number` | `0` | `sparse_infill_filament_id` | |
57
+ | `sparse_infill_flow_ratio` | `number` | `1.0` | `sparse_infill_flow_ratio` | |
54
58
  | `sparse_infill_pattern` | `string` | `'rectilinear'` | `sparse_infill_pattern` | rectilinear\|grid\|triangles\|zigzag\|gyroid |
59
+ | `sparse_infill_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `sparse_infill_speed`, `time_lapse_gcode` | |
55
60
  | `top_shell_layers` | `number` | `4` | `top_shell_layers` | top_shell_layers/bottom_shell_layers |
56
61
 
57
62
  ### Speeds, flow and temperature
58
63
 
59
64
  | Kernel param | Type | Default | From setting | Notes |
60
65
  | --- | --- | --- | --- | --- |
61
- | `bed_temp` | `number` | `60` | `hot_plate_temp` | |
66
+ | `bed_temp` | `number` | `60` | `curr_bed_type`, `hot_plate_temp` | |
62
67
  | `bridge_speed` | `number` | `25.0` | `bridge_speed` | bridge_speed[0] (slowdown for unsupported bottoms) |
63
68
  | `close_fan_the_first_x_layers` | `number` | `1` | `close_fan_the_first_x_layers` | close_fan_the_first_x_layers |
64
69
  | `enable_pressure_advance` | `boolean` | `false` | `enable_pressure_advance` | enable_pressure_advance[0] |
@@ -76,6 +81,10 @@ same unit — `first_layer_height` comes from `initial_layer_print_height`, and
76
81
  | `machine_max_accel_e` | `number` | `5000` | `machine_max_acceleration_e` | mm/s² (machine_max_acceleration_*) — per-axis ceiling, distinct from the accel_* feedrates above |
77
82
  | `machine_max_accel_xy` | `number` | `5000` | `machine_max_acceleration_x` | mm/s² (machine_max_acceleration_*) — per-axis ceiling, distinct from the accel_* feedrates above |
78
83
  | `machine_max_accel_z` | `number` | `500` | `machine_max_acceleration_z` | mm/s² (machine_max_acceleration_*) — per-axis ceiling, distinct from the accel_* feedrates above |
84
+ | `machine_max_acceleration_extruding` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_max_acceleration_extruding`, `machine_start_gcode`, `time_lapse_gcode` | |
85
+ | `machine_max_acceleration_travel` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_max_acceleration_travel`, `machine_start_gcode`, `time_lapse_gcode` | |
86
+ | `machine_max_acceleration_y` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_max_acceleration_y`, `machine_start_gcode`, `time_lapse_gcode` | |
87
+ | `machine_max_jerk_y` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_max_jerk_y`, `machine_start_gcode`, `time_lapse_gcode` | |
79
88
  | `machine_max_speed_e` | `number` | `30` | `machine_max_speed_e` | mm/s |
80
89
  | `machine_max_speed_xy` | `number` | `500` | `machine_max_speed_x` | mm/s |
81
90
  | `machine_max_speed_z` | `number` | `12` | `machine_max_speed_z` | mm/s |
@@ -85,6 +94,7 @@ same unit — `first_layer_height` comes from `initial_layer_print_height`, and
85
94
  | `pressure_advance` | `number` | `0.02` | `pressure_advance` | pressure_advance[0] |
86
95
  | `print_speed` | `number` | `60` | `outer_wall_speed` | |
87
96
  | `slow_down_layer_time` | `number` | `8.0` | `slow_down_layer_time` | slow_down_layer_time (s) |
97
+ | `slow_down_layers` | `number` | `0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `slow_down_layers`, `time_lapse_gcode` | layers over which the first layer's speed ramps up to the print's |
88
98
  | `travel_speed` | `number` | `150` | `travel_speed` | |
89
99
 
90
100
  ### Retraction and seams
@@ -103,6 +113,7 @@ same unit — `first_layer_height` comes from `initial_layer_print_height`, and
103
113
 
104
114
  | Kernel param | Type | Default | From setting | Notes |
105
115
  | --- | --- | --- | --- | --- |
116
+ | `brim_flow_ratio` | `?` | — | `brim_flow_ratio` | undefined |
106
117
  | `brim_object_gap` | `number` | `0.0` | `brim_object_gap` | brim_object_gap (mm): gap between brim and object |
107
118
  | `brim_width` | `number` | `0.0` | `brim_width` | skirt_distance / brim_width |
108
119
  | `raft_contact_distance` | `number` | `0.1` | `raft_contact_distance` | raft_contact_distance (mm) |
@@ -112,6 +123,7 @@ same unit — `first_layer_height` comes from `initial_layer_print_height`, and
112
123
  | `skirt_distance` | `number` | `2.0` | `skirt_distance` | skirt_distance / brim_width |
113
124
  | `skirt_height` | `number` | `1` | `skirt_height` | skirt_height: how many layers the skirt is drawn on |
114
125
  | `skirt_loops` | `number` | `1` | `skirt_loops` | skirt_loops |
126
+ | `skirt_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `skirt_speed`, `time_lapse_gcode` | 0 = follow the role's speed |
115
127
 
116
128
  ### Support
117
129
 
@@ -127,16 +139,20 @@ same unit — `first_layer_height` comes from `initial_layer_print_height`, and
127
139
  | `support_density` | `number` | `0.15` | — | density of the support body |
128
140
  | `support_expansion` | `number` | `0.0` | `support_expansion` | support_expansion (mm): expands the overhang region |
129
141
  | `support_filament` | `number` | `0` | `support_filament` | filament for the support base (and, upstream, the raft) |
142
+ | `support_flow_ratio` | `?` | — | `support_flow_ratio` | undefined |
130
143
  | `support_grid_snap` | `boolean` | `true` | — | equivalent of the upstream SupportGridPattern (default behavior of the grid style) |
131
144
  | `support_interface_bottom_layers` | `number` | `0` | `support_interface_bottom_layers` | support_interface_bottom_layers (0 = none) |
132
145
  | `support_interface_filament` | `number` | `0` | `support_interface_filament` | filament for the support interface |
146
+ | `support_interface_flow_ratio` | `?` | — | `support_interface_flow_ratio` | undefined |
133
147
  | `support_interface_pattern` | `string` | `'auto'` | `support_interface_pattern` | support_interface_pattern: auto\|rectilinear\|concentric\|rectilinear_interlaced\|grid |
134
148
  | `support_interface_spacing` | `number` | `0.5` | `support_interface_spacing` | support_interface_spacing (mm, 0=solid). Upstream default 0.5 |
149
+ | `support_interface_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `support_interface_speed`, `time_lapse_gcode` | |
135
150
  | `support_interface_top_layers` | `number` | `2` | `support_interface_top_layers` | support_interface_top_layers |
136
151
  | `support_object_first_layer_gap` | `number` | `0.2` | `support_object_first_layer_gap` | support_object_first_layer_gap (mm) |
137
152
  | `support_on_build_plate_only` | `boolean` | `false` | `support_on_build_plate_only` | support_on_build_plate_only: only support reaching the bed |
138
153
  | `support_overhang_min_area` | `number` | `0.0` | — | minimum overhang area (mm², 0 = auto w²). Replaces the morphological opening filter |
139
154
  | `support_remove_small_overhang` | `boolean` | `true` | `support_remove_small_overhang` | support_remove_small_overhang (upstream default true) |
155
+ | `support_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `support_speed`, `time_lapse_gcode` | |
140
156
  | `support_style` | `string` | `'grid'` | `support_style`, `support_type` | grid\|tree_lite |
141
157
  | `support_threshold_angle` | `number` | `30.0` | `support_threshold_angle` | support_threshold_angle |
142
158
  | `support_threshold_overlap` | `number` | `0.5` | `support_threshold_overlap` | support_threshold_overlap: the overlap criterion when θ=0 (as a fraction of extrusion width) |
@@ -144,7 +160,7 @@ same unit — `first_layer_height` comes from `initial_layer_print_height`, and
144
160
  | `support_xy_distance` | `number` | `0.35` | `support_object_xy_distance` | support_object_xy_distance |
145
161
  | `tree_lite_min_radius` | `number` | `1.5` | — | tree_lite taper constants (our own approximation — no matching upstream key) |
146
162
  | `tree_lite_shrink` | `number` | `0.5` | — | tree_lite taper constants (our own approximation — no matching upstream key) |
147
- | `tree_style` | `string` | `'organic'` | — | organic\|slim\|strong\|hybrid (upstream support_style smsTree*) |
163
+ | `tree_style` | `string` | `'organic'` | `support_style` | organic\|slim\|strong\|hybrid (upstream support_style smsTree*) |
148
164
  | `tree_support_angle_slow` | `number` | `25.0` | `tree_support_angle_slow` | tree_support_angle_slow (deg) |
149
165
  | `tree_support_branch_angle` | `number` | `40.0` | `tree_support_branch_angle` | fallback spelling — `tree_support_branch_angle_organic` wins when both are sent |
150
166
  | `tree_support_branch_angle_organic` | `number` | `40.0` | `tree_support_branch_angle_organic` | tree_support_branch_angle_organic (deg) |
@@ -178,9 +194,11 @@ same unit — `first_layer_height` comes from `initial_layer_print_height`, and
178
194
 
179
195
  | Kernel param | Type | Default | From setting | Notes |
180
196
  | --- | --- | --- | --- | --- |
181
- | `bed_depth` | `number` | `256.0` | `printable_area` | bed size (offset = bed/2) |
197
+ | `bed_depth` | `number` | `256.0` | `printable_area` | bed size |
182
198
  | `bed_height` | `number` | `0.0` | `printable_height` | printable_height (mm). 0 = no ceiling (backwards compatible) |
183
- | `bed_width` | `number` | `256.0` | `printable_area` | bed size (offset = bed/2) |
199
+ | `bed_origin_x` | `number` | `0.0` | `printable_area` | |
200
+ | `bed_origin_y` | `number` | `0.0` | `printable_area` | |
201
+ | `bed_width` | `number` | `256.0` | `printable_area` | bed size |
184
202
  | `enable_arc_fitting` | `boolean` | `false` | `enable_arc_fitting` | enable_arc_fitting |
185
203
  | `machine_end_gcode` | `string` | — | `machine_end_gcode` | printer profile custom G-code. Empty = the mini-kernel's own preamble/footer only |
186
204
  | `machine_start_gcode` | `string` | — | `machine_start_gcode` | printer profile custom G-code. Empty = the mini-kernel's own preamble/footer only |
@@ -190,39 +208,99 @@ same unit — `first_layer_height` comes from `initial_layer_print_height`, and
190
208
 
191
209
  | Kernel param | Type | Default | From setting | Notes |
192
210
  | --- | --- | --- | --- | --- |
211
+ | `accel_to_decel_enable` | `boolean` | `false` | `accel_to_decel_enable`, `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
212
+ | `accel_to_decel_factor` | `number` | `50.0` | `accel_to_decel_factor`, `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | percent |
213
+ | `alternate_extra_wall` | `boolean` | `false` | `alternate_extra_wall` | alternate_extra_wall |
193
214
  | `arachne_dump` | `boolean` | `false` | — | temporary diagnostic: dump the PASS1 arachne input polygons to stderr |
215
+ | `bottom_solid_infill_flow_ratio` | `number` | `1.0` | `bottom_solid_infill_flow_ratio` | |
216
+ | `bridge_acceleration` | `number` | `0.0` | `before_layer_change_gcode`, `bridge_acceleration`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
217
+ | `bridge_flow` | `number` | `1.0` | `bridge_flow` | |
218
+ | `default_acceleration` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `default_acceleration`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
219
+ | `default_jerk` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `default_jerk`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
220
+ | `detect_thin_wall` | `boolean` | `false` | `detect_thin_wall` | detect_thin_wall: medial-axis thin walls where one loop does not fit |
221
+ | `disable_m73` | `boolean` | `true` | `before_layer_change_gcode`, `change_filament_gcode`, `disable_m73`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
194
222
  | `economy` | `boolean` | `false` | — | |
195
223
  | `emit_pe_tags` | `boolean` | `false` | — | emit PE tags on extrusion runs (enabled automatically when the real PE is used). Default false (backwards compatible) |
196
224
  | `extruder_count` | `number` | `1` | — | multi-material: how many extruders are used (1\|2) |
197
225
  | `extruder_filament_diameter` | `number[]` | `[]` | `filament_diameter` | |
226
+ | `extruder_first_layer_temp` | `number[]` | `[]` | `nozzle_temperature_initial_layer` | nozzle_temperature_initial_layer per extruder (the first layer's own) |
198
227
  | `extruder_flow_ratio` | `number[]` | `[]` | `filament_flow_ratio` | |
199
228
  | `extruder_nozzle_temp` | `number[]` | `[]` | `nozzle_temperature` | |
200
229
  | `extruder_retract_length` | `?` | `[]` | `filament_retraction_length` | undefined |
201
230
  | `extruder_retract_speed` | `?` | `[]` | `filament_retraction_speed` | undefined |
202
231
  | `extruder_z_hop` | `?` | `[]` | `filament_z_hop` | undefined |
203
232
  | `extrusion_rate_slope_segment_length` | `number` | `1.0` | — | segment split length for the real PE (mm) |
233
+ | `filament_max_volumetric_speed` | `number[]` | `[]` | `filament_max_volumetric_speed` | mm³/s, per filament; empty = no cap (GW::capped_feed) |
234
+ | `filter_out_gap_fill` | `number` | `0.0` | `filter_out_gap_fill` | filter_out_gap_fill (mm): gap fill shorter than this is dropped |
235
+ | `first_layer_bed_temp` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `hot_plate_temp_initial_layer`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | the plate type's _initial_layer bed option (<0 = none sent) |
236
+ | `first_layer_flow_ratio` | `?` | — | `first_layer_flow_ratio` | undefined |
237
+ | `first_layer_nozzle_temp` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `nozzle_temperature_initial_layer`, `time_lapse_gcode` | nozzle_temperature_initial_layer (<0 = none sent) |
204
238
  | `flush_into_infill` | `boolean` | `false` | `flush_into_infill` | |
205
239
  | `flush_multiplier` | `number` | `1.0` | `flush_multiplier` | upstream scales the whole table by this |
206
240
  | `flush_volumes_matrix` | `number[]` | `[]` | `flush_volumes_matrix` | |
241
+ | `gap_fill_flow_ratio` | `?` | — | `gap_fill_flow_ratio` | undefined |
242
+ | `gap_infill_speed` | `number[]` | `[]` | `gap_infill_speed` | gap_infill_speed (mm/s, per extruder): a first entry of 0 turns gap fill off (upstream has_gap_fill); empty = the schema's 30 |
207
243
  | `gcode_config_block` | `boolean` | `false` | — | ; CONFIG_BLOCK_START … the parameters this slice ran with … END |
244
+ | `gcode_flavor` | `string` | — | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `gcode_flavor`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
208
245
  | `gcode_role_tags` | `boolean` | `false` | — | ;TYPE:<role> on every role change (upstream GCodeProcessor tag) |
209
246
  | `gcode_stats_block` | `boolean` | `false` | — | ; filament used [mm]/[cm3]/[g], cost, total filament change |
210
247
  | `independent_support_layer_height` | `boolean` | `false` | `independent_support_layer_height` | false by default because of the kernel z grid constraint (gap quantized to layers) |
248
+ | `initial_layer_acceleration` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `initial_layer_acceleration`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
249
+ | `initial_layer_infill_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `initial_layer_infill_speed`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
250
+ | `initial_layer_jerk` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `initial_layer_jerk`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
251
+ | `initial_layer_travel_acceleration` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `initial_layer_travel_acceleration`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
252
+ | `initial_layer_travel_jerk` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `initial_layer_travel_jerk`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
253
+ | `initial_layer_travel_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `initial_layer_travel_speed`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | the first layer's travel speed |
254
+ | `inner_wall_acceleration` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `inner_wall_acceleration`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
255
+ | `inner_wall_flow_ratio` | `number` | `1.0` | `inner_wall_flow_ratio` | |
256
+ | `inner_wall_jerk` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `inner_wall_jerk`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
257
+ | `inner_wall_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `inner_wall_speed`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
258
+ | `internal_solid_infill_acceleration` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `internal_solid_infill_acceleration`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
259
+ | `internal_solid_infill_flow_ratio` | `?` | — | `internal_solid_infill_flow_ratio` | undefined |
260
+ | `internal_solid_infill_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `internal_solid_infill_speed`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
211
261
  | `keep_stages` | `boolean` | `false` | — | keep the stages cached after slicing (skips the early release — a memory trade-off) |
262
+ | `machine_envelope` | `number[]` | `[]` | `before_layer_change_gcode`, `change_filament_gcode`, `emit_machine_limits_to_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
212
263
  | `mm_group_split` | `number` | `0` | — | triangle group boundary index ([0,split)=T0, [split,N)=T1) |
213
264
  | `mm_group_splits` | `number[]` | `[]` | — | |
214
265
  | `mm_group_tools` | `number[]` | `[]` | — | |
266
+ | `only_one_wall_first_layer` | `boolean` | `false` | `only_one_wall_first_layer` | only_one_wall_first_layer |
267
+ | `outer_wall_acceleration` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `outer_wall_acceleration`, `time_lapse_gcode` | |
268
+ | `outer_wall_flow_ratio` | `number` | `1.0` | `outer_wall_flow_ratio` | |
269
+ | `outer_wall_jerk` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `outer_wall_jerk`, `time_lapse_gcode` | |
215
270
  | `pe_external_perimeter_only` | `boolean` | `false` | — | real PE: smooth outer walls only |
216
271
  | `pe_lite` | `boolean` | `true` | — | true = the effective PE-lite (default), false = the ported real PE (tagged g-code) |
217
272
  | `pe_strip_tags` | `boolean` | `true` | — | strip the tags from the final output after real PE post-processing |
273
+ | `placeholder_config` | `string` | — | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode` | |
274
+ | `precise_outer_wall` | `boolean` | `true` | `precise_outer_wall` | precise_outer_wall |
218
275
  | `prime_tower_ring_size` | `number` | `15.0` | — | side length of the fallback square ring (mm) |
219
276
  | `prime_tower_width` | `number` | `30.0` | `prime_tower_width` | width of the real WipeTower (mm). Separate from the square ring width (15). |
220
277
  | `prime_tower_x` | `number` | `10.0` | `wipe_tower_x` | |
221
278
  | `prime_tower_y` | `number` | `10.0` | `wipe_tower_y` | |
222
279
  | `prime_volume` | `number` | `0.0` | `prime_volume` | extra volume primed on every change, on top of the pair's |
280
+ | `print_flow_ratio` | `number` | `1.0` | `print_flow_ratio` | |
223
281
  | `reduce_crossing_wall` | `boolean` | `false` | `reduce_crossing_wall` | wall-avoiding travel |
282
+ | `reduce_infill_retraction` | `boolean` | `false` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `reduce_infill_retraction`, `time_lapse_gcode` | no retraction for a travel inside the infill (GCode::needs_retraction); off = the kernel's own rule |
224
283
  | `reuse_stages` | `number` | `0` | — | |
284
+ | `scarf_joint_flow_ratio` | `?` | — | `scarf_joint_flow_ratio` | undefined |
285
+ | `set_other_flow_ratios` | `boolean` | `false` | `set_other_flow_ratios` | |
286
+ | `single_extruder_multi_material` | `boolean` | `true` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `single_extruder_multi_material`, `time_lapse_gcode` | one nozzle carries every filament: the switch names no tool |
287
+ | `single_tool` | `number` | `0` | — | |
288
+ | `small_perimeter_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `small_perimeter_speed`, `time_lapse_gcode` | mm/s for a wall loop within small_perimeter_threshold; 0 = half the outer wall's |
289
+ | `small_perimeter_threshold` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `small_perimeter_threshold`, `time_lapse_gcode` | a RADIUS (mm): loops up to 2*pi*threshold long are small (0 = off) |
290
+ | `thick_bridges` | `boolean` | `false` | `thick_bridges` | |
225
291
  | `time_engine` | `string` | `'full'` | — | |
292
+ | `top_bottom_infill_wall_overlap` | `number` | `25.0` | `top_bottom_infill_wall_overlap` | top_bottom_infill_wall_overlap (percent, first and topmost layer) |
293
+ | `top_solid_infill_flow_ratio` | `number` | `1.0` | `top_solid_infill_flow_ratio` | |
294
+ | `top_surface_acceleration` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode`, `top_surface_acceleration` | |
295
+ | `top_surface_jerk` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode`, `top_surface_jerk` | |
296
+ | `top_surface_speed` | `number` | `-1.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode`, `top_surface_speed` | |
297
+ | `travel_acceleration` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode`, `travel_acceleration` | |
298
+ | `travel_jerk` | `number` | `0.0` | `before_layer_change_gcode`, `change_filament_gcode`, `filament_end_gcode`, `filament_start_gcode`, `file_start_gcode`, `layer_change_gcode`, `machine_end_gcode`, `machine_start_gcode`, `time_lapse_gcode`, `travel_jerk` | |
299
+ | `use_firmware_retraction` | `boolean` | `false` | `use_firmware_retraction` | G10/G11 instead of E moves (upstream GCodeWriter::_retract) |
300
+ | `use_relative_e_distances` | `boolean` | `true` | `use_relative_e_distances` | false = absolute E (M82, G92 E0 after each retraction), upstream's two modes |
301
+ | `wall_direction` | `string` | `'ccw'` | `wall_direction` | wall_direction (ccw \| cw) |
302
+ | `wall_sequence` | `string` | `'inner wall/outer wall'` | `wall_sequence` | wall_sequence (inner wall/outer wall \| outer wall/inner wall \| inner-outer-inner wall) |
303
+ | `z_offset` | `number` | `0.0` | `z_offset` | added to every Z the G-code writes (upstream GCodeWriter, z_offset) |
226
304
 
227
305
  <!-- KERNEL-PARAMS:END -->
228
306
 
@@ -236,10 +314,10 @@ in the params object yourself. They are not one kind of thing, and the differenc
236
314
  the print. `economy` is the last rung of an out-of-memory retry: it streams G-code and skips the preview
237
315
  toolpaths and the time estimate, so a model that cannot finish any other way still finishes.
238
316
  - **Values the viewer computes** — `extruder_count`, `mm_group_split`, `mm_group_splits`, `mm_group_tools`,
239
- `auto_center`. These come from the scene (how many extruders are loaded, which triangle ranges belong to which
240
- tool), so there is nothing for a settings map to hold. `<Viewport/>` fills them in at slice time.
317
+ `single_tool`, `auto_center`. These come from the scene (how many extruders are loaded, which triangle ranges belong
318
+ to which tool, the one filament a plate's objects all print with), so there is nothing for a settings map to hold. `<Viewport/>` fills them in at slice time.
241
319
  - **Kernel-only tuning** — `support_density`, `support_auto`, `support_grid_snap`, `support_overhang_min_area`,
242
- `tree_style`, `tree_lite_shrink`, `tree_lite_min_radius`, `scarf_length`, `prime_tower_ring_size`,
320
+ `tree_lite_shrink`, `tree_lite_min_radius`, `scarf_length`, `prime_tower_ring_size`,
243
321
  `raft_first_layer_height`, `extrusion_rate_slope_segment_length`. Real knobs with no upstream option of the same
244
322
  name — some are this kernel's own approximations (the `tree_lite_*` taper), others are upstream behaviour that
245
323
  upstream never exposed as a setting.
@@ -0,0 +1,291 @@
1
+ // The front of PASS1 on the GPU: the seated triangles and the layer planes in (the kernel's MESH phase,
2
+ // wasm-core/contour_phase.h), contour_gpu.js's union input out, in GPU buffers, so the contours never leave the GPU
3
+ // between the cut and the union.
4
+ //
5
+ // What runs here (WGSL: contour_front_shaders.js), every layer at once:
6
+ // cut per triangle, the layers it spans (exact z compares), each crossing in double-float, oriented as tri_plane
7
+ // cancel segments coinciding exactly in opposite directions removed in pairs (cancel_coincident), by a hash on place
8
+ // chain each end continues into the smallest unused segment starting at its 1e-3 mm key (chain_polys' greedy rule)
9
+ // loops pointer jumping for each loop's head and each segment's rank; tails that lead into a cycle stay out
10
+ // layout loops sorted by (layer, head), laid out as chain_polys' n + 1 points, write_polygon's duplicates dropped
11
+ // tables layer origins and grids, polygon ranges, the cell-list size
12
+ //
13
+ // Measured (design report gpu-full-port-trial-2026-10-01.md section 3): 3.0M triangles, 420 layers in 30 ms (the kernel's
14
+ // own capture: 410 ms st, 71 ms mt); 0.24 % of the edges differ from the kernel's by its double rounding a shared
15
+ // mesh-edge crossing to 1e-6 mm two ways, which the 48-bit double-float does not reproduce.
16
+ //
17
+ // A layer with a chain that does not close is marked (openLayers): the kernel cuts, chains and unions that layer itself
18
+ // from the mesh it handed over (contour_phase::assemble), so the slice never depends on the GPU closing every loop.
19
+ import {
20
+ CUT_SHADER, CANCEL_SHADER, CHAIN_SHADER, JUMP_SHADER, CYCLES_SHADER, LOOPS_SHADER, OUTPUT_SHADER, WRITE_SHADER,
21
+ TABLES_SHADER, ENTRIES_SHADER, OPEN_LAYERS_SHADER,
22
+ } from './contour_front_shaders.js'
23
+ import { SCAN_SHADER } from './contour_gpu_shaders.js'
24
+
25
+ // Numeric WebGPU constants: the named globals exist in a browser worker but not on a bare node globalThis.
26
+ const BUFFER = { MAP_READ: 0x1, COPY_SRC: 0x4, COPY_DST: 0x8, UNIFORM: 0x40, STORAGE: 0x80 }
27
+ const SHADER_STAGE_COMPUTE = 0x4
28
+ const MAP_MODE_READ = 0x1
29
+ const WORKGROUP_SIZE = 256
30
+ const MAX_WORKGROUPS_PER_DIMENSION = 65535
31
+ const CELL_SIZE = 2_000_000 // the union's grid cell in kernel units; contour_gpu.js and contour_phase.cpp use the same
32
+ const INT32_MAX = 0x7fffffff
33
+ const INT32_MIN = -0x80000000
34
+
35
+ const smallestPowerOfTwo = (count) => 2 ** Math.ceil(Math.log2(Math.max(2, count)))
36
+
37
+ /**
38
+ * @param {GPUDevice} device the device contour_gpu.js runs on (its buffers feed the union directly)
39
+ * @returns {Promise<{ cut(triangles: Float32Array, planes: Float64Array): Promise<object>, dispose(): void }>}
40
+ */
41
+ export async function makeContourFront(device) {
42
+ const BINDING_TYPE = { u: 'uniform', r: 'read-only-storage', w: 'storage' }
43
+ const layouts = new Map(), modules = new Map(), pipelines = new Map(), buffers = new Map()
44
+ let encoder = null, transient = []
45
+
46
+ const layoutOf = (kinds) => {
47
+ if (!layouts.has(kinds)) layouts.set(kinds, device.createBindGroupLayout({ entries: [...kinds].map((kind, binding) => (
48
+ { binding, visibility: SHADER_STAGE_COMPUTE, buffer: { type: BINDING_TYPE[kind] } })) }))
49
+ return layouts.get(kinds)
50
+ }
51
+ const pipelineOf = (code, entryPoint, kinds) => {
52
+ const key = `${entryPoint}\u0000${kinds}\u0000${code}`
53
+ if (!pipelines.has(key)) {
54
+ if (!modules.has(code)) modules.set(code, device.createShaderModule({ code }))
55
+ pipelines.set(key, device.createComputePipeline({ layout: device.createPipelineLayout({ bindGroupLayouts: [layoutOf(kinds)] }),
56
+ compute: { module: modules.get(code), entryPoint } }))
57
+ }
58
+ return pipelines.get(key)
59
+ }
60
+ // compile every shader up front, so a WGSL error surfaces as a refusal instead of an empty result
61
+ for (const code of [CUT_SHADER, CANCEL_SHADER, CHAIN_SHADER, JUMP_SHADER, CYCLES_SHADER, LOOPS_SHADER, OUTPUT_SHADER, WRITE_SHADER, TABLES_SHADER, ENTRIES_SHADER, OPEN_LAYERS_SHADER, SCAN_SHADER]) {
62
+ const module = device.createShaderModule({ code })
63
+ modules.set(code, module)
64
+ const failure = (await module.getCompilationInfo()).messages.find(message => message.type === 'error')
65
+ if (failure) throw new Error(`contour front shader: line ${failure.lineNum}: ${failure.message}`)
66
+ }
67
+
68
+ // a storage buffer kept across slices by name; a regrown one is destroyed after the submit that last used it
69
+ const buffer = (name, bytes) => {
70
+ const size = Math.max(16, Math.ceil(bytes / 16) * 16)
71
+ const kept = buffers.get(name)
72
+ if (kept && kept.size >= size) return kept
73
+ if (kept) transient.push(kept)
74
+ const made = device.createBuffer({ size, usage: BUFFER.STORAGE | BUFFER.COPY_SRC | BUFFER.COPY_DST })
75
+ buffers.set(name, made)
76
+ return made
77
+ }
78
+ const encoderOf = () => { if (!encoder) encoder = device.createCommandEncoder(); return encoder }
79
+ const uniform = (values) => {
80
+ const made = device.createBuffer({ size: Math.max(16, values.length * 4), usage: BUFFER.UNIFORM | BUFFER.COPY_DST })
81
+ device.queue.writeBuffer(made, 0, new Uint32Array(values))
82
+ transient.push(made)
83
+ return made
84
+ }
85
+ const zero = device.createBuffer({ size: 16, usage: BUFFER.UNIFORM | BUFFER.COPY_DST }) // the double-float's `dfZero`
86
+ device.queue.writeBuffer(zero, 0, new Uint32Array(4))
87
+ const run = (code, entryPoint, kinds, bound, invocations) => {
88
+ if (invocations <= 0) return
89
+ let groupsX = Math.ceil(invocations / WORKGROUP_SIZE), groupsY = 1
90
+ if (groupsX > MAX_WORKGROUPS_PER_DIMENSION) { groupsY = Math.ceil(groupsX / MAX_WORKGROUPS_PER_DIMENSION); groupsX = MAX_WORKGROUPS_PER_DIMENSION }
91
+ const group = device.createBindGroup({ layout: layoutOf(kinds), entries: bound.map((target, binding) => ({ binding, resource: { buffer: target } })) })
92
+ const pass = encoderOf().beginComputePass()
93
+ pass.setPipeline(pipelineOf(code, entryPoint, kinds)); pass.setBindGroup(0, group); pass.dispatchWorkgroups(groupsX, groupsY); pass.end()
94
+ }
95
+ const clear = (target, bytes) => encoderOf().clearBuffer(target, 0, Math.ceil(bytes / 4) * 4)
96
+ const copy = (source, sourceOffset, target, targetOffset, bytes) => encoderOf().copyBufferToBuffer(source, sourceOffset, target, targetOffset, bytes)
97
+ const submit = async () => {
98
+ if (encoder) { device.queue.submit([encoder.finish()]); encoder = null }
99
+ await device.queue.onSubmittedWorkDone()
100
+ for (const item of transient) item.destroy()
101
+ transient = []
102
+ }
103
+ // `wordCount` 32-bit words from `offsetWords` on, copied out of the staging buffer
104
+ const readWords = async (source, wordCount, offsetWords = 0, Type = Uint32Array) => {
105
+ const bytes = Math.max(4, wordCount * 4)
106
+ const staging = device.createBuffer({ size: Math.ceil(bytes / 16) * 16, usage: BUFFER.MAP_READ | BUFFER.COPY_DST })
107
+ encoderOf().copyBufferToBuffer(source, offsetWords * 4, staging, 0, Math.ceil(bytes / 4) * 4)
108
+ await submit()
109
+ await staging.mapAsync(MAP_MODE_READ)
110
+ const words = new Type(staging.getMappedRange(0, wordCount * 4).slice(0))
111
+ staging.unmap(); staging.destroy()
112
+ return words
113
+ }
114
+ // in-place exclusive scan of `count` u32 (contour_gpu_shaders.js' scan)
115
+ const scan = (data, count, depth = 0) => {
116
+ const blocks = Math.ceil(count / WORKGROUP_SIZE)
117
+ const sums = buffer(`scan.${depth}`, (blocks + 1) * 4)
118
+ const parameters = uniform([count, 0, 0, 0])
119
+ run(SCAN_SHADER, 'scanBlock', 'uww', [parameters, data, sums], count)
120
+ if (blocks === 1) return
121
+ scan(sums, blocks, depth + 1)
122
+ run(SCAN_SHADER, 'addOff', 'uww', [parameters, data, sums], count)
123
+ }
124
+
125
+ // layers, triangles -> segments (with their chain keys), cancelled pairs, successors
126
+ async function segmentsOf(triangles, planes) {
127
+ const triangleCount = triangles.length / 9, layerCount = planes.length
128
+ const planePairs = new Float32Array(layerCount * 2) // each plane's z as hi + lo, for exact compares with f32 vertices
129
+ for (let layer = 0; layer < layerCount; layer++) {
130
+ const high = Math.fround(planes[layer])
131
+ planePairs[layer * 2] = high; planePairs[layer * 2 + 1] = Math.fround(planes[layer] - high)
132
+ }
133
+ const triangleBuffer = buffer('triangles', triangles.byteLength), planeBuffer = buffer('planes', planePairs.byteLength)
134
+ device.queue.writeBuffer(triangleBuffer, 0, triangles); device.queue.writeBuffer(planeBuffer, 0, planePairs)
135
+ const counts = buffer('counts', (triangleCount + 1) * 4)
136
+ clear(counts, (triangleCount + 1) * 4)
137
+ const unused = [1, 2, 3].map(index => buffer(`unused${index}`, 16))
138
+ run(CUT_SHADER, 'main', 'urrwwwwu', [uniform([triangleCount, layerCount, 0, 0]), triangleBuffer, planeBuffer, counts, ...unused, zero], triangleCount)
139
+ scan(counts, triangleCount + 1)
140
+ const [segmentCount] = await readWords(counts, 1, triangleCount) // the segment count sizes everything after
141
+ const segmentPoints = buffer('segmentPoints', segmentCount * 16), segmentKeys = buffer('segmentKeys', segmentCount * 16)
142
+ const segmentLayer = buffer('segmentLayer', segmentCount * 4)
143
+ run(CUT_SHADER, 'main', 'urrwwwwu', [uniform([triangleCount, layerCount, 1, 0]), triangleBuffer, planeBuffer, counts, segmentPoints, segmentKeys, segmentLayer, zero], triangleCount)
144
+
145
+ const tableSize = smallestPowerOfTwo(segmentCount * 2), mask = tableSize - 1
146
+ const dead = buffer('dead', segmentCount * 4)
147
+ const owner = buffer('owner', tableSize * 4), headA = buffer('headA', tableSize * 4), headB = buffer('headB', tableSize * 4)
148
+ const nextA = buffer('nextA', segmentCount * 4), nextB = buffer('nextB', segmentCount * 4)
149
+ clear(dead, segmentCount * 4); clear(owner, tableSize * 4); clear(headA, tableSize * 4); clear(headB, tableSize * 4)
150
+ const cancelBound = [uniform([segmentCount, mask, 0, 0]), segmentPoints, segmentLayer, owner, headA, headB, nextA, dead]
151
+ run(CANCEL_SHADER, 'insert', 'urrwwwww', cancelBound, segmentCount)
152
+ run(CANCEL_SHADER, 'pair', 'urrwwwww', cancelBound, tableSize)
153
+ clear(owner, tableSize * 4); clear(headA, tableSize * 4); clear(headB, tableSize * 4)
154
+ const successor = buffer('successor', (segmentCount + 2) * 4)
155
+ clear(successor, (segmentCount + 2) * 4)
156
+ const chainBound = [uniform([segmentCount, mask, 0, 0]), segmentKeys, segmentLayer, dead, owner, headA, headB, nextA, nextB, successor]
157
+ run(CHAIN_SHADER, 'insertStarts', 'urrrwwwwww', chainBound, segmentCount)
158
+ run(CHAIN_SHADER, 'insertEnds', 'urrrwwwwww', chainBound, segmentCount)
159
+ run(CHAIN_SHADER, 'pairEnds', 'urrrwwwwww', chainBound, tableSize)
160
+ return { layerCount, segmentCount, segmentPoints, segmentLayer, successor, dead }
161
+ }
162
+
163
+ // segments with successors -> the union's input (contour_gpu.js' layout), still on the GPU
164
+ async function loopsOf(segments) {
165
+ const { layerCount, segmentCount, segmentPoints, segmentLayer, successor, dead } = segments
166
+ // labels (each segment's loop head), then ranks (its distance to the loop's last segment); ping-pong buffers
167
+ const rounds = Math.ceil(Math.log2(Math.max(2, segmentCount))) + 1
168
+ const valueA = buffer('valueA', segmentCount * 4), nextOfA = buffer('nextOfA', segmentCount * 4)
169
+ const valueB = buffer('valueB', segmentCount * 4), nextOfB = buffer('nextOfB', segmentCount * 4)
170
+ const label = buffer('label', segmentCount * 4), rank = buffer('rank', segmentCount * 4), reach = buffer('reach', segmentCount * 4)
171
+ const jump = (mode, input, output) => run(JUMP_SHADER, 'main', 'urrwwwwrr',
172
+ [uniform([segmentCount, mode, 0, 0]), successor, dead, input[0], input[1], output[0], output[1], label, reach], segmentCount)
173
+ const jumpRounds = (mode, finalValue, finalNext) => {
174
+ let from = [valueA, nextOfA], to = [valueB, nextOfB]
175
+ for (let round = 0; round < rounds; round++) { jump(mode, from, to); [from, to] = [to, from] }
176
+ copy(from[0], 0, finalValue, 0, segmentCount * 4)
177
+ if (finalNext) copy(from[1], 0, finalNext, 0, segmentCount * 4)
178
+ }
179
+ jump(0, [valueB, nextOfB], [valueA, nextOfA]); jumpRounds(1, label, reach)
180
+ const hit = buffer('hit', segmentCount * 4)
181
+ clear(hit, segmentCount * 4)
182
+ run(CYCLES_SHADER, 'main', 'uww', [uniform([segmentCount, 0, 0, 0]), reach, hit], segmentCount)
183
+ run(CYCLES_SHADER, 'main', 'uww', [uniform([segmentCount, 1, 0, 0]), reach, hit], segmentCount)
184
+ const openLayer = buffer('openLayer', layerCount * 4)
185
+ clear(openLayer, layerCount * 4)
186
+ run(OPEN_LAYERS_SHADER, 'main', 'urrrw', [uniform([segmentCount, 0, 0, 0]), dead, reach, segmentLayer, openLayer], segmentCount)
187
+ jump(2, [valueB, nextOfB], [valueA, nextOfA]); jumpRounds(3, rank)
188
+
189
+ // heads, sorted by (layer, head) with a bitonic sort
190
+ const headFlag = buffer('headFlag', (segmentCount + 1) * 4)
191
+ clear(headFlag, (segmentCount + 1) * 4)
192
+ const loopBound = (parameters, heads, polygonOf, loopStart, order) => [parameters, successor, label, rank, segmentLayer, headFlag, heads, polygonOf, loopStart, order, reach]
193
+ const unused = [4, 5, 6, 7].map(index => buffer(`unused${index}`, 16))
194
+ run(LOOPS_SHADER, 'flagHeads', 'urrrrwwwwwr', loopBound(uniform([segmentCount, 0, layerCount, 0]), ...unused), segmentCount)
195
+ scan(headFlag, segmentCount + 1)
196
+ const [headCount] = await readWords(headFlag, 1, segmentCount) // the loop count sizes the sort
197
+ const padded = smallestPowerOfTwo(headCount)
198
+ const heads = buffer('heads', padded * 8), polygonOf = buffer('polygonOf', segmentCount * 4)
199
+ const loopStart = buffer('loopStart', (headCount + 1) * 4), order = buffer('order', (segmentCount + headCount) * 4)
200
+ const bound = (parameters) => loopBound(parameters, heads, polygonOf, loopStart, order)
201
+ run(LOOPS_SHADER, 'gatherHeads', 'urrrrwwwwwr', bound(uniform([segmentCount, padded, layerCount, 0])), segmentCount)
202
+ run(LOOPS_SHADER, 'padHeads', 'urrrrwwwwwr', bound(uniform([segmentCount, padded, layerCount, headCount])), padded - headCount)
203
+ for (let block = 2; block <= padded; block *= 2) for (let distance = block / 2; distance >= 1; distance /= 2)
204
+ run(LOOPS_SHADER, 'bitonic', 'urrrrwwwwwr', bound(uniform([segmentCount, padded, distance, block])), padded)
205
+ clear(loopStart, (headCount + 1) * 4)
206
+ run(LOOPS_SHADER, 'lengths', 'urrrrwwwwwr', bound(uniform([segmentCount, padded, layerCount, headCount])), headCount)
207
+ scan(loopStart, headCount + 1)
208
+ const [slotCount] = await readWords(loopStart, 1, headCount) // points of the closed loops, chain_polys' n + 1 each
209
+ run(LOOPS_SHADER, 'place', 'urrrrwwwwwr', bound(uniform([segmentCount, padded, layerCount, headCount])), segmentCount)
210
+
211
+ // the points write_polygon keeps, the polygons chain_polys keeps, the layer boxes
212
+ const kept = buffer('kept', (slotCount + 1) * 4), polygonKept = buffer('polygonKept', (headCount + 1) * 4)
213
+ const layerBox = buffer('layerBox', layerCount * 32), slotPolygon = buffer('slotPolygon', slotCount * 4)
214
+ const boxStart = new Int32Array(layerCount * 8)
215
+ for (let layer = 0; layer < layerCount; layer++) boxStart.set([INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN], layer * 8)
216
+ device.queue.writeBuffer(layerBox, 0, boxStart)
217
+ clear(kept, (slotCount + 1) * 4)
218
+ const outputBound = [uniform([slotCount, headCount, layerCount, 0]), order, segmentPoints, segmentLayer, successor, loopStart, kept, polygonKept, layerBox, slotPolygon]
219
+ run(OUTPUT_SHADER, 'slotPolygons', 'urrrrrwwww', outputBound, headCount)
220
+ run(OUTPUT_SHADER, 'keep', 'urrrrrwwww', outputBound, slotCount)
221
+ run(OUTPUT_SHADER, 'polygonCounts', 'urrrrrwwww', outputBound, headCount)
222
+ run(OUTPUT_SHADER, 'keepValid', 'urrrrrwwww', outputBound, slotCount)
223
+ const polygonValidAt = buffer('polygonValidAt', (headCount + 1) * 4)
224
+ clear(polygonValidAt, (headCount + 1) * 4)
225
+ copy(polygonKept, 0, polygonValidAt, 0, headCount * 4)
226
+ scan(polygonValidAt, headCount + 1)
227
+ const keptAt = buffer('keptAt', (slotCount + 1) * 4)
228
+ clear(keptAt, (slotCount + 1) * 4)
229
+ copy(kept, 0, keptAt, 0, slotCount * 4)
230
+ scan(keptAt, slotCount + 1)
231
+ const [pointCount] = await readWords(keptAt, 1, slotCount)
232
+ const [polygonCount] = await readWords(polygonValidAt, 1, headCount)
233
+ const points = buffer('points', pointCount * 8), layerStart = buffer('layerStart', (layerCount + 1) * 4)
234
+ clear(layerStart, (layerCount + 1) * 4)
235
+ run(WRITE_SHADER, 'main', 'urrrrrrww', [uniform([slotCount, headCount, layerCount, 0]), order, segmentPoints, segmentLayer, kept, keptAt, layerBox, points, layerStart], slotCount)
236
+ scan(layerStart, layerCount + 1)
237
+ const layerInfo = buffer('layerInfo', (layerCount + 1) * 16), polygonInfo = buffer('polygonInfo', polygonCount * 20 + 16)
238
+ const tableBound = [uniform([slotCount, headCount, layerCount, CELL_SIZE]), layerBox, layerStart, layerInfo, polygonValidAt, loopStart, keptAt, order, segmentLayer, polygonInfo]
239
+ run(TABLES_SHADER, 'cells', 'urwwrrrrrw', tableBound, 1)
240
+ run(TABLES_SHADER, 'polygons', 'urwwrrrrrw', tableBound, headCount)
241
+ const entries = buffer('entries', 16)
242
+ clear(entries, 16)
243
+ run(ENTRIES_SHADER, 'main', 'urrw', [uniform([polygonCount, CELL_SIZE, 0, 0]), polygonInfo, points, entries], polygonCount)
244
+ const [cellCount] = await readWords(layerInfo, 1, layerCount * 4)
245
+ const [cellEntryCount] = await readWords(entries, 1)
246
+ const boxes = await readWords(layerBox, layerCount * 8, 0, Int32Array)
247
+ const openLayers = await readWords(openLayer, layerCount)
248
+ // the origin the kernel adds back to every piece; a layer with no loop has none, and the kernel's own is 0 there
249
+ const origins = new Int32Array(layerCount * 2)
250
+ for (let layer = 0; layer < layerCount; layer++) {
251
+ if (boxes[layer * 8 + 2] < boxes[layer * 8]) continue
252
+ origins[layer * 2] = boxes[layer * 8]; origins[layer * 2 + 1] = boxes[layer * 8 + 1]
253
+ }
254
+ return { layerCount, segmentCount: pointCount, polygonCount, cellCount, cellEntryCount, headCount, slotCount, origins, openLayers,
255
+ points, layerInfo, polygonInfo }
256
+ }
257
+
258
+ /**
259
+ * The union input of every layer of a mesh.
260
+ * @param {Float32Array} triangles 9 floats per triangle, seated (the kernel's contour_mesh().tris)
261
+ * @param {Float64Array} planes the z of every layer plane (contour_mesh().planes)
262
+ * @returns {Promise<object>} counts, `origins` (min x, min y per layer, kernel units), `openLayers` (1 per layer with a
263
+ * segment left out of every closed loop: that layer's input is incomplete and the kernel rebuilds it), `openSegments`
264
+ * and `input`, contour_gpu.js' union input with its arrays as GPU buffers
265
+ */
266
+ const cut = async (triangles, planes) => {
267
+ const started = performance.now()
268
+ const segments = await segmentsOf(triangles, planes)
269
+ const [matcherOverflow] = await readWords(segments.successor, 1, segments.segmentCount)
270
+ const deadFlags = await readWords(segments.dead, segments.segmentCount)
271
+ let cancelled = 0
272
+ for (const flag of deadFlags) cancelled += flag
273
+ const laid = await loopsOf(segments)
274
+ const loopSegments = laid.slotCount - laid.headCount
275
+ return {
276
+ ms: performance.now() - started, triangleCount: triangles.length / 9, rawSegments: segments.segmentCount, cancelled,
277
+ openSegments: segments.segmentCount - cancelled - loopSegments, matcherOverflow, origins: laid.origins, openLayers: laid.openLayers,
278
+ input: { layerCount: laid.layerCount, segmentCount: laid.segmentCount, polygonCount: laid.polygonCount, cellCount: laid.cellCount,
279
+ cellEntryCount: laid.cellEntryCount, points: { gpuBuffer: laid.points }, layerInfo: { gpuBuffer: laid.layerInfo },
280
+ polyInfo: { gpuBuffer: laid.polygonInfo }, polyLayer: { gpuBuffer: laid.polygonInfo, offset: laid.polygonCount * 16 } },
281
+ }
282
+ }
283
+ const dispose = () => {
284
+ for (const kept of buffers.values()) kept.destroy()
285
+ buffers.clear()
286
+ for (const item of transient) item.destroy()
287
+ transient = []
288
+ zero.destroy()
289
+ }
290
+ return { cut, dispose }
291
+ }