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/CHANGELOG.md +134 -0
- package/README.md +6 -0
- package/data/config-schema-lean.json +1 -1
- package/data/config-schema.json +6 -34
- package/data/filaments.js +1 -1
- package/data/printers.json +1 -1
- package/data/processes.js +1 -1
- package/engine/PARAMS.md +86 -8
- package/engine/src/contour_front_gpu.js +291 -0
- package/engine/src/contour_front_shaders.js +449 -0
- package/engine/src/contour_gpu.js +367 -0
- package/engine/src/contour_gpu_shaders.js +450 -0
- package/engine/src/contour_slice.js +131 -0
- package/engine/src/settings.js +25 -13
- package/engine/src/slicer.worker.js +50 -4
- package/engine/src/slicer_core.js +0 -0
- package/engine/src/slicer_core.mt.js +0 -0
- package/engine/src/slicer_core.mt.wasm +0 -0
- package/package.json +2 -2
- package/types/data/filaments.js.d.ts +6 -1
- package/types/data/printers.json.d.ts +13 -3
- package/types/data/processes.js.d.ts +6 -1
- package/types/settings.d.ts +16 -1
- package/types/worker.d.ts +4 -1
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 **
|
|
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'` |
|
|
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
|
|
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
|
-
| `
|
|
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
|
|
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
|
-
`
|
|
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
|
+
}
|